From 8fe0c527f33fe6d2e89d62a23ae912553572452b Mon Sep 17 00:00:00 2001 From: Stefano Sabatini Date: Sat, 28 May 2011 21:51:03 +0200 Subject: lavfi: add LUT (LookUp Table) generic filters Signed-off-by: Anton Khirnov --- libavfilter/Makefile | 3 + libavfilter/allfilters.c | 3 + libavfilter/avfilter.h | 4 +- libavfilter/formats.c | 12 ++ libavfilter/internal.h | 3 + libavfilter/vf_lut.c | 371 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 libavfilter/vf_lut.c (limited to 'libavfilter') diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 4bd06e9f2f..9659fd692f 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -33,6 +33,9 @@ OBJS-$(CONFIG_FREI0R_FILTER) += vf_frei0r.o OBJS-$(CONFIG_GRADFUN_FILTER) += vf_gradfun.o OBJS-$(CONFIG_HFLIP_FILTER) += vf_hflip.o OBJS-$(CONFIG_HQDN3D_FILTER) += vf_hqdn3d.o +OBJS-$(CONFIG_LUT_FILTER) += vf_lut.o +OBJS-$(CONFIG_LUTRGB_FILTER) += vf_lut.o +OBJS-$(CONFIG_LUTYUV_FILTER) += vf_lut.o OBJS-$(CONFIG_NOFORMAT_FILTER) += vf_format.o OBJS-$(CONFIG_NULL_FILTER) += vf_null.o OBJS-$(CONFIG_OCV_FILTER) += vf_libopencv.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index dbbe441859..1357322302 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -54,6 +54,9 @@ void avfilter_register_all(void) REGISTER_FILTER (GRADFUN, gradfun, vf); REGISTER_FILTER (HFLIP, hflip, vf); REGISTER_FILTER (HQDN3D, hqdn3d, vf); + REGISTER_FILTER (LUT, lut, vf); + REGISTER_FILTER (LUTRGB, lutrgb, vf); + REGISTER_FILTER (LUTYUV, lutyuv, vf); REGISTER_FILTER (NOFORMAT, noformat, vf); REGISTER_FILTER (NULL, null, vf); REGISTER_FILTER (OCV, ocv, vf); diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index c46dd1f5dd..d4c03792da 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -29,8 +29,8 @@ #include "libavutil/rational.h" #define LIBAVFILTER_VERSION_MAJOR 2 -#define LIBAVFILTER_VERSION_MINOR 7 -#define LIBAVFILTER_VERSION_MICRO 2 +#define LIBAVFILTER_VERSION_MINOR 8 +#define LIBAVFILTER_VERSION_MICRO 0 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ LIBAVFILTER_VERSION_MINOR, \ diff --git a/libavfilter/formats.c b/libavfilter/formats.c index ae916cf16c..848b2ee119 100644 --- a/libavfilter/formats.c +++ b/libavfilter/formats.c @@ -21,6 +21,7 @@ #include "libavutil/pixdesc.h" #include "avfilter.h" +#include "internal.h" /** * Add all refs from a to ret and destroy a. @@ -70,6 +71,17 @@ AVFilterFormats *avfilter_merge_formats(AVFilterFormats *a, AVFilterFormats *b) return ret; } +int ff_fmt_is_in(int fmt, const int *fmts) +{ + const int *p; + + for (p = fmts; *p != PIX_FMT_NONE; p++) { + if (fmt == *p) + return 1; + } + return 0; +} + AVFilterFormats *avfilter_make_format_list(const int *fmts) { AVFilterFormats *formats; diff --git a/libavfilter/internal.h b/libavfilter/internal.h index 64b3f3b865..0630e9b7d6 100644 --- a/libavfilter/internal.h +++ b/libavfilter/internal.h @@ -52,4 +52,7 @@ int ff_avfilter_graph_config_formats(AVFilterGraph *graphctx, AVClass *log_ctx); /** default handler for freeing audio/video buffer when there are no references left */ void ff_avfilter_default_free_buffer(AVFilterBuffer *buf); +/** Tell is a format is contained in the provided list terminated by -1. */ +int ff_fmt_is_in(int fmt, const int *fmts); + #endif /* AVFILTER_INTERNAL_H */ diff --git a/libavfilter/vf_lut.c b/libavfilter/vf_lut.c new file mode 100644 index 0000000000..d7fab5e17d --- /dev/null +++ b/libavfilter/vf_lut.c @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2011 Stefano Sabatini + * + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Compute a look-up table for binding the input value to the output + * value, and apply it to input video. + */ + +#include "libavutil/eval.h" +#include "libavutil/mathematics.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avfilter.h" +#include "internal.h" + +static const char *var_names[] = { + "E", + "PHI", + "PI", + "w", ///< width of the input video + "h", ///< height of the input video + "val", ///< input value for the pixel + "maxval", ///< max value for the pixel + "minval", ///< min value for the pixel + "negval", ///< negated value + "clipval", + NULL +}; + +enum var_name { + VAR_E, + VAR_PHI, + VAR_PI, + VAR_W, + VAR_H, + VAR_VAL, + VAR_MAXVAL, + VAR_MINVAL, + VAR_NEGVAL, + VAR_CLIPVAL, + VAR_VARS_NB +}; + +typedef struct { + const AVClass *class; + uint8_t lut[4][256]; ///< lookup table for each component + char *comp_expr_str[4]; + AVExpr *comp_expr[4]; + int hsub, vsub; + double var_values[VAR_VARS_NB]; + int is_rgb, is_yuv; + int rgba_map[4]; + int step; +} LutContext; + +#define Y 0 +#define U 1 +#define V 2 +#define R 0 +#define G 1 +#define B 2 +#define A 3 + +#define OFFSET(x) offsetof(LutContext, x) + +static const AVOption lut_options[] = { + {"c0", "set component #0 expression", OFFSET(comp_expr_str[0]), FF_OPT_TYPE_STRING, {.str="val"}, CHAR_MIN, CHAR_MAX}, + {"c1", "set component #1 expression", OFFSET(comp_expr_str[1]), FF_OPT_TYPE_STRING, {.str="val"}, CHAR_MIN, CHAR_MAX}, + {"c2", "set component #2 expression", OFFSET(comp_expr_str[2]), FF_OPT_TYPE_STRING, {.str="val"}, CHAR_MIN, CHAR_MAX}, + {"c3", "set component #3 expression", OFFSET(comp_expr_str[3]), FF_OPT_TYPE_STRING, {.str="val"}, CHAR_MIN, CHAR_MAX}, + {"y", "set Y expression", OFFSET(comp_expr_str[Y]), FF_OPT_TYPE_STRING, {.str="val"}, CHAR_MIN, CHAR_MAX}, + {"u", "set U expression", OFFSET(comp_expr_str[U]), FF_OPT_TYPE_STRING, {.str="val"}, CHAR_MIN, CHAR_MAX}, + {"v", "set V expression", OFFSET(comp_expr_str[V]), FF_OPT_TYPE_STRING, {.str="val"}, CHAR_MIN, CHAR_MAX}, + {"r", "set R expression", OFFSET(comp_expr_str[R]), FF_OPT_TYPE_STRING, {.str="val"}, CHAR_MIN, CHAR_MAX}, + {"g", "set G expression", OFFSET(comp_expr_str[G]), FF_OPT_TYPE_STRING, {.str="val"}, CHAR_MIN, CHAR_MAX}, + {"b", "set B expression", OFFSET(comp_expr_str[B]), FF_OPT_TYPE_STRING, {.str="val"}, CHAR_MIN, CHAR_MAX}, + {"a", "set A expression", OFFSET(comp_expr_str[A]), FF_OPT_TYPE_STRING, {.str="val"}, CHAR_MIN, CHAR_MAX}, + {NULL}, +}; + +static const char *lut_get_name(void *ctx) +{ + return "lut"; +} + +static const AVClass lut_class = { + "LutContext", + lut_get_name, + lut_options +}; + +static int init(AVFilterContext *ctx, const char *args, void *opaque) +{ + LutContext *lut = ctx->priv; + int ret; + + lut->class = &lut_class; + av_opt_set_defaults(lut); + + lut->var_values[VAR_PHI] = M_PHI; + lut->var_values[VAR_PI] = M_PI; + lut->var_values[VAR_E ] = M_E; + + lut->is_rgb = !strcmp(ctx->filter->name, "lutrgb"); + lut->is_yuv = !strcmp(ctx->filter->name, "lutyuv"); + if (args && (ret = av_set_options_string(lut, args, "=", ":")) < 0) + return ret; + + return 0; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + LutContext *lut = ctx->priv; + int i; + + for (i = 0; i < 4; i++) { + av_expr_free(lut->comp_expr[i]); + lut->comp_expr[i] = NULL; + av_freep(&lut->comp_expr_str[i]); + } +} + +#define YUV_FORMATS \ + PIX_FMT_YUV444P, PIX_FMT_YUV422P, PIX_FMT_YUV420P, \ + PIX_FMT_YUV411P, PIX_FMT_YUV410P, PIX_FMT_YUV440P, \ + PIX_FMT_YUVA420P, \ + PIX_FMT_YUVJ444P, PIX_FMT_YUVJ422P, PIX_FMT_YUVJ420P, \ + PIX_FMT_YUVJ440P + +#define RGB_FORMATS \ + PIX_FMT_ARGB, PIX_FMT_RGBA, \ + PIX_FMT_ABGR, PIX_FMT_BGRA, \ + PIX_FMT_RGB24, PIX_FMT_BGR24 + +static enum PixelFormat yuv_pix_fmts[] = { YUV_FORMATS, PIX_FMT_NONE }; +static enum PixelFormat rgb_pix_fmts[] = { RGB_FORMATS, PIX_FMT_NONE }; +static enum PixelFormat all_pix_fmts[] = { RGB_FORMATS, YUV_FORMATS, PIX_FMT_NONE }; + +static int query_formats(AVFilterContext *ctx) +{ + LutContext *lut = ctx->priv; + + enum PixelFormat *pix_fmts = lut->is_rgb ? rgb_pix_fmts : + lut->is_yuv ? yuv_pix_fmts : all_pix_fmts; + + avfilter_set_common_formats(ctx, avfilter_make_format_list(pix_fmts)); + return 0; +} + +/** + * Clip value val in the minval - maxval range. + */ +static double clip(void *opaque, double val) +{ + LutContext *lut = opaque; + double minval = lut->var_values[VAR_MINVAL]; + double maxval = lut->var_values[VAR_MAXVAL]; + + return av_clip(val, minval, maxval); +} + +/** + * Compute gamma correction for value val, assuming the minval-maxval + * range, val is clipped to a value contained in the same interval. + */ +static double compute_gammaval(void *opaque, double gamma) +{ + LutContext *lut = opaque; + double val = lut->var_values[VAR_CLIPVAL]; + double minval = lut->var_values[VAR_MINVAL]; + double maxval = lut->var_values[VAR_MAXVAL]; + + return pow((val-minval)/(maxval-minval), gamma) * (maxval-minval)+minval; +} + +static double (* const funcs1[])(void *, double) = { + clip, + compute_gammaval, + NULL +}; + +static const char * const funcs1_names[] = { + "clip", + "gammaval", + NULL +}; + +static int config_props(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + LutContext *lut = ctx->priv; + const AVPixFmtDescriptor *desc = &av_pix_fmt_descriptors[inlink->format]; + int min[4], max[4]; + int val, comp, ret; + + lut->hsub = desc->log2_chroma_w; + lut->vsub = desc->log2_chroma_h; + + lut->var_values[VAR_W] = inlink->w; + lut->var_values[VAR_H] = inlink->h; + + switch (inlink->format) { + case PIX_FMT_YUV410P: + case PIX_FMT_YUV411P: + case PIX_FMT_YUV420P: + case PIX_FMT_YUV422P: + case PIX_FMT_YUV440P: + case PIX_FMT_YUV444P: + case PIX_FMT_YUVA420P: + min[Y] = min[U] = min[V] = 16; + max[Y] = 235; + max[U] = max[V] = 240; + min[A] = 0; max[A] = 255; + break; + default: + min[0] = min[1] = min[2] = min[3] = 0; + max[0] = max[1] = max[2] = max[3] = 255; + } + + lut->is_yuv = lut->is_rgb = 0; + if (ff_fmt_is_in(inlink->format, yuv_pix_fmts)) lut->is_yuv = 1; + else if (ff_fmt_is_in(inlink->format, rgb_pix_fmts)) lut->is_rgb = 1; + + if (lut->is_rgb) { + switch (inlink->format) { + case PIX_FMT_ARGB: lut->rgba_map[A] = 0; lut->rgba_map[R] = 1; lut->rgba_map[G] = 2; lut->rgba_map[B] = 3; break; + case PIX_FMT_ABGR: lut->rgba_map[A] = 0; lut->rgba_map[B] = 1; lut->rgba_map[G] = 2; lut->rgba_map[R] = 3; break; + case PIX_FMT_RGBA: + case PIX_FMT_RGB24: lut->rgba_map[R] = 0; lut->rgba_map[G] = 1; lut->rgba_map[B] = 2; lut->rgba_map[A] = 3; break; + case PIX_FMT_BGRA: + case PIX_FMT_BGR24: lut->rgba_map[B] = 0; lut->rgba_map[G] = 1; lut->rgba_map[R] = 2; lut->rgba_map[A] = 3; break; + } + lut->step = av_get_bits_per_pixel(desc) >> 3; + } + + for (comp = 0; comp < desc->nb_components; comp++) { + double res; + + /* create the parsed expression */ + ret = av_expr_parse(&lut->comp_expr[comp], lut->comp_expr_str[comp], + var_names, funcs1_names, funcs1, NULL, NULL, 0, ctx); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, + "Error when parsing the expression '%s' for the component %d.\n", + lut->comp_expr_str[comp], comp); + return AVERROR(EINVAL); + } + + /* compute the lut */ + lut->var_values[VAR_MAXVAL] = max[comp]; + lut->var_values[VAR_MINVAL] = min[comp]; + + for (val = 0; val < 256; val++) { + lut->var_values[VAR_VAL] = val; + lut->var_values[VAR_CLIPVAL] = av_clip(val, min[comp], max[comp]); + lut->var_values[VAR_NEGVAL] = + av_clip(min[comp] + max[comp] - lut->var_values[VAR_VAL], + min[comp], max[comp]); + + res = av_expr_eval(lut->comp_expr[comp], lut->var_values, lut); + if (isnan(res)) { + av_log(ctx, AV_LOG_ERROR, + "Error when evaluating the expression '%s' for the value %d for the component #%d.\n", + lut->comp_expr_str[comp], val, comp); + return AVERROR(EINVAL); + } + lut->lut[comp][val] = av_clip((int)res, min[comp], max[comp]); + av_log(ctx, AV_LOG_DEBUG, "val[%d][%d] = %d\n", comp, val, lut->lut[comp][val]); + } + } + + return 0; +} + +static void draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir) +{ + AVFilterContext *ctx = inlink->dst; + LutContext *lut = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + AVFilterBufferRef *inpic = inlink ->cur_buf; + AVFilterBufferRef *outpic = outlink->out_buf; + uint8_t *inrow, *outrow, *inrow0, *outrow0; + int i, j, k, plane; + + if (lut->is_rgb) { + /* packed */ + inrow0 = inpic ->data[0] + y * inpic ->linesize[0]; + outrow0 = outpic->data[0] + y * outpic->linesize[0]; + + for (i = 0; i < h; i ++) { + inrow = inrow0; + outrow = outrow0; + for (j = 0; j < inlink->w; j++) { + for (k = 0; k < lut->step; k++) + outrow[k] = lut->lut[lut->rgba_map[k]][inrow[k]]; + outrow += lut->step; + inrow += lut->step; + } + inrow0 += inpic ->linesize[0]; + outrow0 += outpic->linesize[0]; + } + } else { + /* planar */ + for (plane = 0; plane < 4 && inpic->data[plane]; plane++) { + int vsub = plane == 1 || plane == 2 ? lut->vsub : 0; + int hsub = plane == 1 || plane == 2 ? lut->hsub : 0; + + inrow = inpic ->data[plane] + (y>>vsub) * inpic ->linesize[plane]; + outrow = outpic->data[plane] + (y>>vsub) * outpic->linesize[plane]; + + for (i = 0; i < h>>vsub; i ++) { + for (j = 0; j < inlink->w>>hsub; j++) + outrow[j] = lut->lut[plane][inrow[j]]; + inrow += inpic ->linesize[plane]; + outrow += outpic->linesize[plane]; + } + } + } + + avfilter_draw_slice(outlink, y, h, slice_dir); +} + +#define DEFINE_LUT_FILTER(name_, description_, init_) \ + AVFilter avfilter_vf_##name_ = { \ + .name = #name_, \ + .description = NULL_IF_CONFIG_SMALL(description_), \ + .priv_size = sizeof(LutContext), \ + \ + .init = init_, \ + .uninit = uninit, \ + .query_formats = query_formats, \ + \ + .inputs = (AVFilterPad[]) {{ .name = "default", \ + .type = AVMEDIA_TYPE_VIDEO, \ + .draw_slice = draw_slice, \ + .config_props = config_props, \ + .min_perms = AV_PERM_READ, }, \ + { .name = NULL}}, \ + .outputs = (AVFilterPad[]) {{ .name = "default", \ + .type = AVMEDIA_TYPE_VIDEO, }, \ + { .name = NULL}}, \ + } + +#if CONFIG_LUT_FILTER +DEFINE_LUT_FILTER(lut, "Compute and apply a lookup table to the RGB/YUV input video.", init); +#endif +#if CONFIG_LUTYUV_FILTER +DEFINE_LUT_FILTER(lutyuv, "Compute and apply a lookup table to the YUV input video.", init); +#endif +#if CONFIG_LUTRGB_FILTER +DEFINE_LUT_FILTER(lutrgb, "Compute and apply a lookup table to the RGB input video.", init); +#endif -- cgit v1.2.3 From 171868e25a231ff06864e9f37400680148af10b4 Mon Sep 17 00:00:00 2001 From: Stefano Sabatini Date: Sat, 28 May 2011 22:00:26 +0200 Subject: lavfi: add negate filter This filter is a simple wrapper around the LUT filter. Signed-off-by: Anton Khirnov --- doc/filters.texi | 7 ++++++- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/avfilter.h | 2 +- libavfilter/vf_lut.c | 23 +++++++++++++++++++++++ 5 files changed, 32 insertions(+), 2 deletions(-) (limited to 'libavfilter') diff --git a/doc/filters.texi b/doc/filters.texi index f7aacf2e19..c71fd48b6e 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -813,7 +813,12 @@ format=rgba,lutrgb=a="maxval-minval/2" lutyuv=y=gammaval(0.5) @end example -@section noformat +@section negate + +Negate input video. + +This filter accepts an integer in input, if non-zero it negates the +alpha component (if available). The default value in input is 0. Force libavfilter not to use any of the specified pixel formats for the input to the next filter. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 9659fd692f..e2285e4d39 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -36,6 +36,7 @@ OBJS-$(CONFIG_HQDN3D_FILTER) += vf_hqdn3d.o OBJS-$(CONFIG_LUT_FILTER) += vf_lut.o OBJS-$(CONFIG_LUTRGB_FILTER) += vf_lut.o OBJS-$(CONFIG_LUTYUV_FILTER) += vf_lut.o +OBJS-$(CONFIG_NEGATE_FILTER) += vf_lut.o OBJS-$(CONFIG_NOFORMAT_FILTER) += vf_format.o OBJS-$(CONFIG_NULL_FILTER) += vf_null.o OBJS-$(CONFIG_OCV_FILTER) += vf_libopencv.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 1357322302..ba853c7178 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -57,6 +57,7 @@ void avfilter_register_all(void) REGISTER_FILTER (LUT, lut, vf); REGISTER_FILTER (LUTRGB, lutrgb, vf); REGISTER_FILTER (LUTYUV, lutyuv, vf); + REGISTER_FILTER (NEGATE, negate, vf); REGISTER_FILTER (NOFORMAT, noformat, vf); REGISTER_FILTER (NULL, null, vf); REGISTER_FILTER (OCV, ocv, vf); diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index d4c03792da..66237c56b4 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -29,7 +29,7 @@ #include "libavutil/rational.h" #define LIBAVFILTER_VERSION_MAJOR 2 -#define LIBAVFILTER_VERSION_MINOR 8 +#define LIBAVFILTER_VERSION_MINOR 9 #define LIBAVFILTER_VERSION_MICRO 0 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ diff --git a/libavfilter/vf_lut.c b/libavfilter/vf_lut.c index d7fab5e17d..7f3e913945 100644 --- a/libavfilter/vf_lut.c +++ b/libavfilter/vf_lut.c @@ -69,6 +69,7 @@ typedef struct { int is_rgb, is_yuv; int rgba_map[4]; int step; + int negate_alpha; /* only used by negate */ } LutContext; #define Y 0 @@ -369,3 +370,25 @@ DEFINE_LUT_FILTER(lutyuv, "Compute and apply a lookup table to the YUV input vid #if CONFIG_LUTRGB_FILTER DEFINE_LUT_FILTER(lutrgb, "Compute and apply a lookup table to the RGB input video.", init); #endif + +#if CONFIG_NEGATE_FILTER + +static int negate_init(AVFilterContext *ctx, const char *args, void *opaque) +{ + LutContext *lut = ctx->priv; + char lut_params[64]; + + if (args) + sscanf(args, "%d", &lut->negate_alpha); + + av_log(ctx, AV_LOG_DEBUG, "negate_alpha:%d\n", lut->negate_alpha); + + snprintf(lut_params, sizeof(lut_params), "c0=negval:c1=negval:c2=negval:a=%s", + lut->negate_alpha ? "negval" : "val"); + + return init(ctx, lut_params, opaque); +} + +DEFINE_LUT_FILTER(negate, "Negate input video.", negate_init); + +#endif -- cgit v1.2.3 From ce6b6ef645f6ac17f78e937837fe8f37a1b2beaf Mon Sep 17 00:00:00 2001 From: Stefano Sabatini Date: Sat, 9 Jul 2011 18:13:10 +0200 Subject: lavfi: port boxblur filter from libmpcodecs With the following additions: * support to gray format * support to yuva420p format * parametric luma/chroma/alpha radius * consistency check on the radius values, avoid crashes with invalid values Signed-off-by: Anton Khirnov --- Changelog | 1 + configure | 1 + doc/filters.texi | 60 ++++++++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/avfilter.h | 2 +- libavfilter/vf_boxblur.c | 350 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 libavfilter/vf_boxblur.c (limited to 'libavfilter') diff --git a/Changelog b/Changelog index 8374e05342..a70a3db252 100644 --- a/Changelog +++ b/Changelog @@ -51,6 +51,7 @@ easier to use. The changes are: - CELT in Ogg demuxing - VC-1 interlaced decoding - lut, lutrgb, and lutyuv filters +- boxblur filter version 0.7: diff --git a/configure b/configure index dbbb86c095..720b625237 100755 --- a/configure +++ b/configure @@ -1480,6 +1480,7 @@ udp_protocol_deps="network" # filters blackframe_filter_deps="gpl" +boxblur_filter_deps="gpl" cropdetect_filter_deps="gpl" drawtext_filter_deps="libfreetype" frei0r_filter_deps="frei0r dlopen strtok_r" diff --git a/doc/filters.texi b/doc/filters.texi index c71fd48b6e..f3f77183ca 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -183,6 +183,66 @@ threshold, and defaults to 98. @var{threshold} is the threshold below which a pixel value is considered black, and defaults to 32. +@section boxblur + +Apply boxblur algorithm to the input video. + +This filter accepts the parameters: +@var{luma_power}:@var{luma_radius}:@var{chroma_radius}:@var{chroma_power}:@var{alpha_radius}:@var{alpha_power} + +Chroma and alpha parameters are optional, if not specified they default +to the corresponding values set for @var{luma_radius} and +@var{luma_power}. + +@var{luma_radius}, @var{chroma_radius}, and @var{alpha_radius} represent +the radius in pixels of the box used for blurring the corresponding +input plane. They are expressions, and can contain the following +constants: +@table @option +@item w, h +the input width and heigth in pixels + +@item cw, ch +the input chroma image width and height in pixels + +@item hsub, vsub +horizontal and vertical chroma subsample values. For example for the +pixel format "yuv422p" @var{hsub} is 2 and @var{vsub} is 1. +@end table + +The radius must be a non-negative number, and must not be greater than +the value of the expression @code{min(w,h)/2} for the luma and alpha planes, +and of @code{min(cw,ch)/2} for the chroma planes. + +@var{luma_power}, @var{chroma_power}, and @var{alpha_power} represent +how many times the boxblur filter is applied to the corresponding +plane. + +Some examples follow: + +@itemize + +@item +Apply a boxblur filter with luma, chroma, and alpha radius +set to 2: +@example +boxblur=2:1 +@end example + +@item +Set luma radius to 2, alpha and chroma radius to 0 +@example +boxblur=2:1:0:0:0:0 +@end example + +@item +Set luma and chroma radius to a fraction of the video dimension +@example +boxblur=min(h\,w)/10:1:min(cw\,ch)/10:1 +@end example + +@end itemize + @section copy Copy the input source unchanged to the output. Mainly useful for diff --git a/libavfilter/Makefile b/libavfilter/Makefile index e2285e4d39..44f2ab94d4 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -20,6 +20,7 @@ OBJS-$(CONFIG_ANULLSRC_FILTER) += asrc_anullsrc.o OBJS-$(CONFIG_ANULLSINK_FILTER) += asink_anullsink.o OBJS-$(CONFIG_BLACKFRAME_FILTER) += vf_blackframe.o +OBJS-$(CONFIG_BOXBLUR_FILTER) += vf_boxblur.o OBJS-$(CONFIG_COPY_FILTER) += vf_copy.o OBJS-$(CONFIG_CROP_FILTER) += vf_crop.o OBJS-$(CONFIG_CROPDETECT_FILTER) += vf_cropdetect.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index ba853c7178..f9746cd53e 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -41,6 +41,7 @@ void avfilter_register_all(void) REGISTER_FILTER (ANULLSINK, anullsink, asink); REGISTER_FILTER (BLACKFRAME, blackframe, vf); + REGISTER_FILTER (BOXBLUR, boxblur, vf); REGISTER_FILTER (COPY, copy, vf); REGISTER_FILTER (CROP, crop, vf); REGISTER_FILTER (CROPDETECT, cropdetect, vf); diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index 66237c56b4..4ed8ed9f1d 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -29,7 +29,7 @@ #include "libavutil/rational.h" #define LIBAVFILTER_VERSION_MAJOR 2 -#define LIBAVFILTER_VERSION_MINOR 9 +#define LIBAVFILTER_VERSION_MINOR 10 #define LIBAVFILTER_VERSION_MICRO 0 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ diff --git a/libavfilter/vf_boxblur.c b/libavfilter/vf_boxblur.c new file mode 100644 index 0000000000..a314f5c2c1 --- /dev/null +++ b/libavfilter/vf_boxblur.c @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2002 Michael Niedermayer + * Copyright (c) 2011 Stefano Sabatini + * + * This file is part of Libav. + * + * Libav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Libav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with Libav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * @file + * Apply a boxblur filter to the input video. + * Ported from MPlayer libmpcodecs/vf_boxblur.c. + */ + +#include "libavutil/avstring.h" +#include "libavutil/eval.h" +#include "libavutil/pixdesc.h" +#include "avfilter.h" + +static const char *var_names[] = { + "w", + "h", + "cw", + "ch", + "hsub", + "vsub", + NULL +}; + +enum var_name { + VAR_W, + VAR_H, + VAR_CW, + VAR_CH, + VAR_HSUB, + VAR_VSUB, + VARS_NB +}; + +typedef struct { + int radius; + int power; +} FilterParam; + +typedef struct { + FilterParam luma_param; + FilterParam chroma_param; + FilterParam alpha_param; + char luma_radius_expr [256]; + char chroma_radius_expr[256]; + char alpha_radius_expr [256]; + + int hsub, vsub; + int radius[4]; + int power[4]; + uint8_t *temp[2]; ///< temporary buffer used in blur_power() +} BoxBlurContext; + +#define Y 0 +#define U 1 +#define V 2 +#define A 3 + +static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque) +{ + BoxBlurContext *boxblur = ctx->priv; + int e; + + if (!args) { + av_log(ctx, AV_LOG_ERROR, + "Filter expects 2 or 4 or 6 arguments, none provided\n"); + return AVERROR(EINVAL); + } + + e = sscanf(args, "%255[^:]:%d:%255[^:]:%d:%255[^:]:%d", + boxblur->luma_radius_expr, &boxblur->luma_param .power, + boxblur->chroma_radius_expr, &boxblur->chroma_param.power, + boxblur->alpha_radius_expr, &boxblur->alpha_param .power); + + if (e != 2 && e != 4 && e != 6) { + av_log(ctx, AV_LOG_ERROR, + "Filter expects 2 or 4 or 6 params, provided %d\n", e); + return AVERROR(EINVAL); + } + + if (e < 4) { + boxblur->chroma_param.power = boxblur->luma_param.power; + av_strlcpy(boxblur->chroma_radius_expr, boxblur->luma_radius_expr, + sizeof(boxblur->chroma_radius_expr)); + } + if (e < 6) { + boxblur->alpha_param.power = boxblur->luma_param.power; + av_strlcpy(boxblur->alpha_radius_expr, boxblur->luma_radius_expr, + sizeof(boxblur->alpha_radius_expr)); + } + + return 0; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + BoxBlurContext *boxblur = ctx->priv; + + av_freep(&boxblur->temp[0]); + av_freep(&boxblur->temp[1]); +} + +static int query_formats(AVFilterContext *ctx) +{ + enum PixelFormat pix_fmts[] = { + PIX_FMT_YUV444P, PIX_FMT_YUV422P, PIX_FMT_YUV420P, + PIX_FMT_YUV411P, PIX_FMT_YUV410P, PIX_FMT_YUVA420P, + PIX_FMT_YUV440P, PIX_FMT_GRAY8, + PIX_FMT_YUVJ444P, PIX_FMT_YUVJ422P, PIX_FMT_YUVJ420P, + PIX_FMT_YUVJ440P, + PIX_FMT_NONE + }; + + avfilter_set_common_formats(ctx, avfilter_make_format_list(pix_fmts)); + return 0; +} + +static int config_input(AVFilterLink *inlink) +{ + const AVPixFmtDescriptor *desc = &av_pix_fmt_descriptors[inlink->format]; + AVFilterContext *ctx = inlink->dst; + BoxBlurContext *boxblur = ctx->priv; + int w = inlink->w, h = inlink->h; + int cw, ch; + double var_values[VARS_NB], res; + char *expr; + int ret; + + av_freep(&boxblur->temp[0]); + av_freep(&boxblur->temp[1]); + if (!(boxblur->temp[0] = av_malloc(FFMAX(w, h)))) + return AVERROR(ENOMEM); + if (!(boxblur->temp[1] = av_malloc(FFMAX(w, h)))) { + av_freep(&boxblur->temp[0]); + return AVERROR(ENOMEM); + } + + boxblur->hsub = desc->log2_chroma_w; + boxblur->vsub = desc->log2_chroma_h; + + var_values[VAR_W] = inlink->w; + var_values[VAR_H] = inlink->h; + var_values[VAR_CW] = cw = w>>boxblur->hsub; + var_values[VAR_CH] = ch = h>>boxblur->vsub; + var_values[VAR_HSUB] = 1<hsub; + var_values[VAR_VSUB] = 1<vsub; + +#define EVAL_RADIUS_EXPR(comp) \ + expr = boxblur->comp##_radius_expr; \ + ret = av_expr_parse_and_eval(&res, expr, var_names, var_values, \ + NULL, NULL, NULL, NULL, NULL, 0, ctx); \ + boxblur->comp##_param.radius = res; \ + if (ret < 0) { \ + av_log(NULL, AV_LOG_ERROR, \ + "Error when evaluating " #comp " radius expression '%s'\n", expr); \ + return ret; \ + } + EVAL_RADIUS_EXPR(luma); + EVAL_RADIUS_EXPR(chroma); + EVAL_RADIUS_EXPR(alpha); + + av_log(ctx, AV_LOG_DEBUG, + "luma_radius:%d luma_power:%d " + "chroma_radius:%d chroma_power:%d " + "alpha_radius:%d alpha_power:%d " + "w:%d chroma_w:%d h:%d chroma_h:%d\n", + boxblur->luma_param .radius, boxblur->luma_param .power, + boxblur->chroma_param.radius, boxblur->chroma_param.power, + boxblur->alpha_param .radius, boxblur->alpha_param .power, + w, cw, h, ch); + +#define CHECK_RADIUS_VAL(w_, h_, comp) \ + if (boxblur->comp##_param.radius < 0 || \ + 2*boxblur->comp##_param.radius > FFMIN(w_, h_)) { \ + av_log(ctx, AV_LOG_ERROR, \ + "Invalid " #comp " radius value %d, must be >= 0 and <= %d\n", \ + boxblur->comp##_param.radius, FFMIN(w_, h_)/2); \ + return AVERROR(EINVAL); \ + } + CHECK_RADIUS_VAL(w, h, luma); + CHECK_RADIUS_VAL(cw, ch, chroma); + CHECK_RADIUS_VAL(w, h, alpha); + + boxblur->radius[Y] = boxblur->luma_param.radius; + boxblur->radius[U] = boxblur->radius[V] = boxblur->chroma_param.radius; + boxblur->radius[A] = boxblur->alpha_param.radius; + + boxblur->power[Y] = boxblur->luma_param.power; + boxblur->power[U] = boxblur->power[V] = boxblur->chroma_param.power; + boxblur->power[A] = boxblur->alpha_param.power; + + return 0; +} + +static inline void blur(uint8_t *dst, int dst_step, const uint8_t *src, int src_step, + int len, int radius) +{ + /* Naive boxblur would sum source pixels from x-radius .. x+radius + * for destination pixel x. That would be O(radius*width). + * If you now look at what source pixels represent 2 consecutive + * output pixels, then you see they are almost identical and only + * differ by 2 pixels, like: + * src0 111111111 + * dst0 1 + * src1 111111111 + * dst1 1 + * src0-src1 1 -1 + * so when you know one output pixel you can find the next by just adding + * and subtracting 1 input pixel. + * The following code adopts this faster variant. + */ + const int length = radius*2 + 1; + const int inv = ((1<<16) + length/2)/length; + int x, sum = 0; + + for (x = 0; x < radius; x++) + sum += src[x*src_step]<<1; + sum += src[radius*src_step]; + + for (x = 0; x <= radius; x++) { + sum += src[(radius+x)*src_step] - src[(radius-x)*src_step]; + dst[x*dst_step] = (sum*inv + (1<<15))>>16; + } + + for (; x < len-radius; x++) { + sum += src[(radius+x)*src_step] - src[(x-radius-1)*src_step]; + dst[x*dst_step] = (sum*inv + (1<<15))>>16; + } + + for (; x < len; x++) { + sum += src[(2*len-radius-x-1)*src_step] - src[(x-radius-1)*src_step]; + dst[x*dst_step] = (sum*inv + (1<<15))>>16; + } +} + +static inline void blur_power(uint8_t *dst, int dst_step, const uint8_t *src, int src_step, + int len, int radius, int power, uint8_t *temp[2]) +{ + uint8_t *a = temp[0], *b = temp[1]; + + if (radius && power) { + blur(a, 1, src, src_step, len, radius); + for (; power > 2; power--) { + uint8_t *c; + blur(b, 1, a, 1, len, radius); + c = a; a = b; b = c; + } + if (power > 1) { + blur(dst, dst_step, a, 1, len, radius); + } else { + int i; + for (i = 0; i < len; i++) + dst[i*dst_step] = a[i]; + } + } else { + int i; + for (i = 0; i < len; i++) + dst[i*dst_step] = src[i*src_step]; + } +} + +static void hblur(uint8_t *dst, int dst_linesize, const uint8_t *src, int src_linesize, + int w, int h, int radius, int power, uint8_t *temp[2]) +{ + int y; + + if (radius == 0 && dst == src) + return; + + for (y = 0; y < h; y++) + blur_power(dst + y*dst_linesize, 1, src + y*src_linesize, 1, + w, radius, power, temp); +} + +static void vblur(uint8_t *dst, int dst_linesize, const uint8_t *src, int src_linesize, + int w, int h, int radius, int power, uint8_t *temp[2]) +{ + int x; + + if (radius == 0 && dst == src) + return; + + for (x = 0; x < w; x++) + blur_power(dst + x, dst_linesize, src + x, src_linesize, + h, radius, power, temp); +} + +static void draw_slice(AVFilterLink *inlink, int y0, int h0, int slice_dir) +{ + AVFilterContext *ctx = inlink->dst; + BoxBlurContext *boxblur = ctx->priv; + AVFilterLink *outlink = inlink->dst->outputs[0]; + AVFilterBufferRef *inpicref = inlink ->cur_buf; + AVFilterBufferRef *outpicref = outlink->out_buf; + int plane; + int cw = inlink->w >> boxblur->hsub, ch = h0 >> boxblur->vsub; + int w[4] = { inlink->w, cw, cw, inlink->w }; + int h[4] = { h0, ch, ch, h0 }; + + for (plane = 0; inpicref->data[plane] && plane < 4; plane++) + hblur(outpicref->data[plane], outpicref->linesize[plane], + inpicref ->data[plane], inpicref ->linesize[plane], + w[plane], h[plane], boxblur->radius[plane], boxblur->power[plane], + boxblur->temp); + + for (plane = 0; inpicref->data[plane] && plane < 4; plane++) + vblur(outpicref->data[plane], outpicref->linesize[plane], + outpicref->data[plane], outpicref->linesize[plane], + w[plane], h[plane], boxblur->radius[plane], boxblur->power[plane], + boxblur->temp); + + avfilter_draw_slice(outlink, y0, h0, slice_dir); +} + +AVFilter avfilter_vf_boxblur = { + .name = "boxblur", + .description = NULL_IF_CONFIG_SMALL("Blur the input."), + .priv_size = sizeof(BoxBlurContext), + .init = init, + .uninit = uninit, + .query_formats = query_formats, + + .inputs = (AVFilterPad[]) {{ .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_input, + .draw_slice = draw_slice, + .min_perms = AV_PERM_READ }, + { .name = NULL}}, + .outputs = (AVFilterPad[]) {{ .name = "default", + .type = AVMEDIA_TYPE_VIDEO, }, + { .name = NULL}}, +}; -- cgit v1.2.3 From b157be1f383e55f86324dae11e90a7b59173eec1 Mon Sep 17 00:00:00 2001 From: Stefano Sabatini Date: Wed, 10 Aug 2011 18:58:49 +0200 Subject: lavfi: port libmpcodecs delogo filter The ported filter supports named option parsing and more YUV formats. Signed-off-by: Anton Khirnov --- configure | 1 + doc/filters.texi | 52 +++++++++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/avfilter.h | 2 +- libavfilter/vf_delogo.c | 284 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 libavfilter/vf_delogo.c (limited to 'libavfilter') diff --git a/configure b/configure index 720b625237..a036873682 100755 --- a/configure +++ b/configure @@ -1482,6 +1482,7 @@ udp_protocol_deps="network" blackframe_filter_deps="gpl" boxblur_filter_deps="gpl" cropdetect_filter_deps="gpl" +delogo_filter_deps="gpl" drawtext_filter_deps="libfreetype" frei0r_filter_deps="frei0r dlopen strtok_r" frei0r_src_filter_deps="frei0r dlopen strtok_r" diff --git a/doc/filters.texi b/doc/filters.texi index f3f77183ca..56f00ff4e1 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -381,6 +381,58 @@ indicates never reset and return the largest area encountered during playback. @end table +@section delogo + +Suppress a TV station logo by a simple interpolation of the surrounding +pixels. Just set a rectangle covering the logo and watch it disappear +(and sometimes something even uglier appear - your mileage may vary). + +The filter accepts parameters as a string of the form +"@var{x}:@var{y}:@var{w}:@var{h}:@var{band}", or as a list of +@var{key}=@var{value} pairs, separated by ":". + +The description of the accepted parameters follows. + +@table @option + +@item x, y +Specify the top left corner coordinates of the logo. They must be +specified. + +@item w, h +Specify the width and height of the logo to clear. They must be +specified. + +@item band, t +Specify the thickness of the fuzzy edge of the rectangle (added to +@var{w} and @var{h}). The default value is 4. + +@item show +When set to 1, a green rectangle is drawn on the screen to simplify +finding the right @var{x}, @var{y}, @var{w}, @var{h} parameters, and +@var{band} is set to 4. The default value is 0. + +@end table + +Some examples follow. + +@itemize + +@item +Set a rectangle covering the area with top left corner coordinates 0,0 +and size 100x77, setting a band of size 10: +@example +delogo=0:0:100:77:10 +@end example + +@item +As the previous example, but use named options: +@example +delogo=x=0:y=0:w=100:h=77:band=10 +@end example + +@end itemize + @section drawbox Draw a colored box on the input image. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 44f2ab94d4..c5f146e949 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -24,6 +24,7 @@ OBJS-$(CONFIG_BOXBLUR_FILTER) += vf_boxblur.o OBJS-$(CONFIG_COPY_FILTER) += vf_copy.o OBJS-$(CONFIG_CROP_FILTER) += vf_crop.o OBJS-$(CONFIG_CROPDETECT_FILTER) += vf_cropdetect.o +OBJS-$(CONFIG_DELOGO_FILTER) += vf_delogo.o OBJS-$(CONFIG_DRAWBOX_FILTER) += vf_drawbox.o OBJS-$(CONFIG_DRAWTEXT_FILTER) += vf_drawtext.o OBJS-$(CONFIG_FADE_FILTER) += vf_fade.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index f9746cd53e..f6c60a1eea 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -45,6 +45,7 @@ void avfilter_register_all(void) REGISTER_FILTER (COPY, copy, vf); REGISTER_FILTER (CROP, crop, vf); REGISTER_FILTER (CROPDETECT, cropdetect, vf); + REGISTER_FILTER (DELOGO, delogo, vf); REGISTER_FILTER (DRAWBOX, drawbox, vf); REGISTER_FILTER (DRAWTEXT, drawtext, vf); REGISTER_FILTER (FADE, fade, vf); diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index 4ed8ed9f1d..72b5dfa12b 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -29,7 +29,7 @@ #include "libavutil/rational.h" #define LIBAVFILTER_VERSION_MAJOR 2 -#define LIBAVFILTER_VERSION_MINOR 10 +#define LIBAVFILTER_VERSION_MINOR 11 #define LIBAVFILTER_VERSION_MICRO 0 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ diff --git a/libavfilter/vf_delogo.c b/libavfilter/vf_delogo.c new file mode 100644 index 0000000000..ca3156814b --- /dev/null +++ b/libavfilter/vf_delogo.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2002 Jindrich Makovicka + * Copyright (c) 2011 Stefano Sabatini + * + * This file is part of Libav. + * + * Libav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Libav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with Libav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * @file + * A very simple tv station logo remover + * Ported from MPlayer libmpcodecs/vf_delogo.c. + */ + +#include "libavutil/imgutils.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avfilter.h" + +/** + * Apply a simple delogo algorithm to the image in dst and put the + * result in src. + * + * The algorithm is only applied to the region specified by the logo + * parameters. + * + * @param w width of the input image + * @param h height of the input image + * @param logo_x x coordinate of the top left corner of the logo region + * @param logo_y y coordinate of the top left corner of the logo region + * @param logo_w width of the logo + * @param logo_h height of the logo + * @param band the size of the band around the processed area + * @param show show a rectangle around the processed area, useful for + * parameters tweaking + * @param direct if non-zero perform in-place processing + */ +static void apply_delogo(uint8_t *dst, int dst_linesize, + uint8_t *src, int src_linesize, + int w, int h, + int logo_x, int logo_y, int logo_w, int logo_h, + int band, int show, int direct) +{ + int x, y; + int interp, dist; + uint8_t *xdst, *xsrc; + + uint8_t *topleft, *botleft, *topright; + int xclipl, xclipr, yclipt, yclipb; + int logo_x1, logo_x2, logo_y1, logo_y2; + + xclipl = FFMAX(-logo_x, 0); + xclipr = FFMAX(logo_x+logo_w-w, 0); + yclipt = FFMAX(-logo_y, 0); + yclipb = FFMAX(logo_y+logo_h-h, 0); + + logo_x1 = logo_x + xclipl; + logo_x2 = logo_x + logo_w - xclipr; + logo_y1 = logo_y + yclipt; + logo_y2 = logo_y + logo_h - yclipb; + + topleft = src+logo_y1 * src_linesize+logo_x1; + topright = src+logo_y1 * src_linesize+logo_x2-1; + botleft = src+(logo_y2-1) * src_linesize+logo_x1; + + dst += (logo_y1+1)*dst_linesize; + src += (logo_y1+1)*src_linesize; + + if (!direct) + av_image_copy_plane(dst, dst_linesize, src, src_linesize, w, h); + + for (y = logo_y1+1; y < logo_y2-1; y++) { + for (x = logo_x1+1, + xdst = dst+logo_x1+1, + xsrc = src+logo_x1+1; x < logo_x2-1; x++, xdst++, xsrc++) { + interp = (topleft[src_linesize*(y-logo_y -yclipt)] + + topleft[src_linesize*(y-logo_y-1-yclipt)] + + topleft[src_linesize*(y-logo_y+1-yclipt)]) * (logo_w-(x-logo_x))/logo_w + + (topright[src_linesize*(y-logo_y-yclipt)] + + topright[src_linesize*(y-logo_y-1-yclipt)] + + topright[src_linesize*(y-logo_y+1-yclipt)]) * (x-logo_x)/logo_w + + (topleft[x-logo_x-xclipl] + + topleft[x-logo_x-1-xclipl] + + topleft[x-logo_x+1-xclipl]) * (logo_h-(y-logo_y))/logo_h + + (botleft[x-logo_x-xclipl] + + botleft[x-logo_x-1-xclipl] + + botleft[x-logo_x+1-xclipl]) * (y-logo_y)/logo_h; + interp /= 6; + + if (y >= logo_y+band && y < logo_y+logo_h-band && + x >= logo_x+band && x < logo_x+logo_w-band) { + *xdst = interp; + } else { + dist = 0; + if (x < logo_x+band) + dist = FFMAX(dist, logo_x-x+band); + else if (x >= logo_x+logo_w-band) + dist = FFMAX(dist, x-(logo_x+logo_w-1-band)); + + if (y < logo_y+band) + dist = FFMAX(dist, logo_y-y+band); + else if (y >= logo_y+logo_h-band) + dist = FFMAX(dist, y-(logo_y+logo_h-1-band)); + + *xdst = (*xsrc*dist + interp*(band-dist))/band; + if (show && (dist == band-1)) + *xdst = 0; + } + } + + dst += dst_linesize; + src += src_linesize; + } +} + +typedef struct { + const AVClass *class; + int x, y, w, h, band, show; +} DelogoContext; + +#define OFFSET(x) offsetof(DelogoContext, x) + +static const AVOption delogo_options[]= { + {"x", "set logo x position", OFFSET(x), FF_OPT_TYPE_INT, {-1}, -1, INT_MAX }, + {"y", "set logo y position", OFFSET(y), FF_OPT_TYPE_INT, {-1}, -1, INT_MAX }, + {"w", "set logo width", OFFSET(w), FF_OPT_TYPE_INT, {-1}, -1, INT_MAX }, + {"h", "set logo height", OFFSET(h), FF_OPT_TYPE_INT, {-1}, -1, INT_MAX }, + {"band", "set delogo area band size", OFFSET(band), FF_OPT_TYPE_INT, { 4}, -1, INT_MAX }, + {"t", "set delogo area band size", OFFSET(band), FF_OPT_TYPE_INT, { 4}, -1, INT_MAX }, + {"show", "show delogo area", OFFSET(show), FF_OPT_TYPE_INT, { 0}, 0, 1 }, + {NULL}, +}; + +static const char *delogo_get_name(void *ctx) +{ + return "delogo"; +} + +static const AVClass delogo_class = { + .class_name = "DelogoContext", + .item_name = delogo_get_name, + .option = delogo_options, +}; + +static int query_formats(AVFilterContext *ctx) +{ + enum PixelFormat pix_fmts[] = { + PIX_FMT_YUV444P, PIX_FMT_YUV422P, PIX_FMT_YUV420P, + PIX_FMT_YUV411P, PIX_FMT_YUV410P, PIX_FMT_YUV440P, + PIX_FMT_YUVA420P, PIX_FMT_GRAY8, + PIX_FMT_NONE + }; + + avfilter_set_common_formats(ctx, avfilter_make_format_list(pix_fmts)); + return 0; +} + +static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque) +{ + DelogoContext *delogo = ctx->priv; + int ret = 0; + + delogo->class = &delogo_class; + av_opt_set_defaults(delogo); + + if (args) + ret = sscanf(args, "%d:%d:%d:%d:%d", + &delogo->x, &delogo->y, &delogo->w, &delogo->h, &delogo->band); + if (ret == 5) { + if (delogo->band < 0) + delogo->show = 1; + } else if ((ret = (av_set_options_string(delogo, args, "=", ":"))) < 0) { + av_log(ctx, AV_LOG_ERROR, "Error parsing options string: '%s'\n", args); + return ret; + } + +#define CHECK_UNSET_OPT(opt) \ + if (delogo->opt == -1) { \ + av_log(delogo, AV_LOG_ERROR, "Option %s was not set.\n", #opt); \ + return AVERROR(EINVAL); \ + } + CHECK_UNSET_OPT(x); + CHECK_UNSET_OPT(y); + CHECK_UNSET_OPT(w); + CHECK_UNSET_OPT(h); + + if (delogo->show) + delogo->band = 4; + + av_log(ctx, AV_LOG_DEBUG, "x:%d y:%d, w:%d h:%d band:%d show:%d\n", + delogo->x, delogo->y, delogo->w, delogo->h, delogo->band, delogo->show); + + delogo->w += delogo->band*2; + delogo->h += delogo->band*2; + delogo->x -= delogo->band; + delogo->y -= delogo->band; + + return 0; +} + +static void start_frame(AVFilterLink *inlink, AVFilterBufferRef *inpicref) +{ + AVFilterLink *outlink = inlink->dst->outputs[0]; + AVFilterBufferRef *outpicref; + + if (inpicref->perms & AV_PERM_PRESERVE) { + outpicref = avfilter_get_video_buffer(outlink, AV_PERM_WRITE, + outlink->w, outlink->h); + avfilter_copy_buffer_ref_props(outpicref, inpicref); + outpicref->video->w = outlink->w; + outpicref->video->h = outlink->h; + } else + outpicref = inpicref; + + outlink->out_buf = outpicref; + avfilter_start_frame(outlink, avfilter_ref_buffer(outpicref, ~0)); +} + +static void null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { } + +static void end_frame(AVFilterLink *inlink) +{ + DelogoContext *delogo = inlink->dst->priv; + AVFilterLink *outlink = inlink->dst->outputs[0]; + AVFilterBufferRef *inpicref = inlink ->cur_buf; + AVFilterBufferRef *outpicref = outlink->out_buf; + int direct = inpicref == outpicref; + int hsub0 = av_pix_fmt_descriptors[inlink->format].log2_chroma_w; + int vsub0 = av_pix_fmt_descriptors[inlink->format].log2_chroma_h; + int plane; + + for (plane = 0; plane < 4 && inpicref->data[plane]; plane++) { + int hsub = plane == 1 || plane == 2 ? hsub0 : 0; + int vsub = plane == 1 || plane == 2 ? vsub0 : 0; + + apply_delogo(outpicref->data[plane], outpicref->linesize[plane], + inpicref ->data[plane], inpicref ->linesize[plane], + inlink->w>>hsub, inlink->h>>vsub, + delogo->x>>hsub, delogo->y>>vsub, + delogo->w>>hsub, delogo->h>>vsub, + delogo->band>>FFMIN(hsub, vsub), + delogo->show, direct); + } + + avfilter_draw_slice(outlink, 0, inlink->h, 1); + avfilter_end_frame(outlink); + avfilter_unref_buffer(inpicref); + if (!direct) + avfilter_unref_buffer(outpicref); +} + +AVFilter avfilter_vf_delogo = { + .name = "delogo", + .description = NULL_IF_CONFIG_SMALL("Remove logo from input video."), + .priv_size = sizeof(DelogoContext), + .init = init, + .query_formats = query_formats, + + .inputs = (AVFilterPad[]) {{ .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .get_video_buffer = avfilter_null_get_video_buffer, + .start_frame = start_frame, + .draw_slice = null_draw_slice, + .end_frame = end_frame, + .min_perms = AV_PERM_WRITE | AV_PERM_READ, + .rej_perms = AV_PERM_PRESERVE }, + { .name = NULL}}, + .outputs = (AVFilterPad[]) {{ .name = "default", + .type = AVMEDIA_TYPE_VIDEO, }, + { .name = NULL}}, +}; -- cgit v1.2.3