yading@10: /* yading@10: * Copyright (C) 2006 Michael Niedermayer yading@10: * Copyright (C) 2012 Clément Bœsch yading@10: * yading@10: * This file is part of FFmpeg. yading@10: * yading@10: * FFmpeg is free software; you can redistribute it and/or modify yading@10: * it under the terms of the GNU General Public License as published by yading@10: * the Free Software Foundation; either version 2 of the License, or yading@10: * (at your option) any later version. yading@10: * yading@10: * FFmpeg is distributed in the hope that it will be useful, yading@10: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the yading@10: * GNU General Public License for more details. yading@10: * yading@10: * You should have received a copy of the GNU General Public License along yading@10: * with FFmpeg; if not, write to the Free Software Foundation, Inc., yading@10: * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. yading@10: */ yading@10: yading@10: /** yading@10: * @file yading@10: * Generic equation change filter yading@10: * Originally written by Michael Niedermayer for the MPlayer project, and yading@10: * ported by Clément Bœsch for FFmpeg. yading@10: */ yading@10: yading@10: #include "libavutil/avstring.h" yading@10: #include "libavutil/eval.h" yading@10: #include "libavutil/opt.h" yading@10: #include "libavutil/pixdesc.h" yading@10: #include "internal.h" yading@10: yading@10: typedef struct { yading@10: const AVClass *class; yading@10: AVExpr *e[4]; ///< expressions for each plane yading@10: char *expr_str[4]; ///< expression strings for each plane yading@10: int framenum; ///< frame counter yading@10: AVFrame *picref; ///< current input buffer yading@10: int hsub, vsub; ///< chroma subsampling yading@10: int planes; ///< number of planes yading@10: } GEQContext; yading@10: yading@10: #define OFFSET(x) offsetof(GEQContext, x) yading@10: #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM yading@10: yading@10: static const AVOption geq_options[] = { yading@10: { "lum_expr", "set luminance expression", OFFSET(expr_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, yading@10: { "cb_expr", "set chroma blue expression", OFFSET(expr_str[1]), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, yading@10: { "cr_expr", "set chroma red expression", OFFSET(expr_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, yading@10: { "alpha_expr", "set alpha expression", OFFSET(expr_str[3]), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, yading@10: {NULL}, yading@10: }; yading@10: yading@10: AVFILTER_DEFINE_CLASS(geq); yading@10: yading@10: static inline double getpix(void *priv, double x, double y, int plane) yading@10: { yading@10: int xi, yi; yading@10: GEQContext *geq = priv; yading@10: AVFrame *picref = geq->picref; yading@10: const uint8_t *src = picref->data[plane]; yading@10: const int linesize = picref->linesize[plane]; yading@10: const int w = picref->width >> ((plane == 1 || plane == 2) ? geq->hsub : 0); yading@10: const int h = picref->height >> ((plane == 1 || plane == 2) ? geq->vsub : 0); yading@10: yading@10: if (!src) yading@10: return 0; yading@10: yading@10: xi = x = av_clipf(x, 0, w - 2); yading@10: yi = y = av_clipf(y, 0, h - 2); yading@10: yading@10: x -= xi; yading@10: y -= yi; yading@10: yading@10: return (1-y)*((1-x)*src[xi + yi * linesize] + x*src[xi + 1 + yi * linesize]) yading@10: + y *((1-x)*src[xi + (yi+1) * linesize] + x*src[xi + 1 + (yi+1) * linesize]); yading@10: } yading@10: yading@10: //TODO: cubic interpolate yading@10: //TODO: keep the last few frames yading@10: static double lum(void *priv, double x, double y) { return getpix(priv, x, y, 0); } yading@10: static double cb(void *priv, double x, double y) { return getpix(priv, x, y, 1); } yading@10: static double cr(void *priv, double x, double y) { return getpix(priv, x, y, 2); } yading@10: static double alpha(void *priv, double x, double y) { return getpix(priv, x, y, 3); } yading@10: yading@10: static const char *const var_names[] = { "X", "Y", "W", "H", "N", "SW", "SH", "T", NULL }; yading@10: enum { VAR_X, VAR_Y, VAR_W, VAR_H, VAR_N, VAR_SW, VAR_SH, VAR_T, VAR_VARS_NB }; yading@10: yading@10: static av_cold int geq_init(AVFilterContext *ctx) yading@10: { yading@10: GEQContext *geq = ctx->priv; yading@10: int plane, ret = 0; yading@10: yading@10: if (!geq->expr_str[0]) { yading@10: av_log(ctx, AV_LOG_ERROR, "Luminance expression is mandatory\n"); yading@10: ret = AVERROR(EINVAL); yading@10: goto end; yading@10: } yading@10: yading@10: if (!geq->expr_str[1] && !geq->expr_str[2]) { yading@10: /* No chroma at all: fallback on luma */ yading@10: geq->expr_str[1] = av_strdup(geq->expr_str[0]); yading@10: geq->expr_str[2] = av_strdup(geq->expr_str[0]); yading@10: } else { yading@10: /* One chroma unspecified, fallback on the other */ yading@10: if (!geq->expr_str[1]) geq->expr_str[1] = av_strdup(geq->expr_str[2]); yading@10: if (!geq->expr_str[2]) geq->expr_str[2] = av_strdup(geq->expr_str[1]); yading@10: } yading@10: yading@10: if (!geq->expr_str[3]) yading@10: geq->expr_str[3] = av_strdup("255"); yading@10: yading@10: if (!geq->expr_str[1] || !geq->expr_str[2] || !geq->expr_str[3]) { yading@10: ret = AVERROR(ENOMEM); yading@10: goto end; yading@10: } yading@10: yading@10: for (plane = 0; plane < 4; plane++) { yading@10: static double (*p[])(void *, double, double) = { lum, cb, cr, alpha }; yading@10: static const char *const func2_names[] = { "lum", "cb", "cr", "alpha", "p", NULL }; yading@10: double (*func2[])(void *, double, double) = { lum, cb, cr, alpha, p[plane], NULL }; yading@10: yading@10: ret = av_expr_parse(&geq->e[plane], geq->expr_str[plane], var_names, yading@10: NULL, NULL, func2_names, func2, 0, ctx); yading@10: if (ret < 0) yading@10: break; yading@10: } yading@10: yading@10: end: yading@10: return ret; yading@10: } yading@10: yading@10: static int geq_query_formats(AVFilterContext *ctx) yading@10: { yading@10: static const enum PixelFormat pix_fmts[] = { yading@10: AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P, yading@10: AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV440P, yading@10: AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA420P, yading@10: AV_PIX_FMT_GRAY8, yading@10: AV_PIX_FMT_NONE yading@10: }; yading@10: ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); yading@10: return 0; yading@10: } yading@10: yading@10: static int geq_config_props(AVFilterLink *inlink) yading@10: { yading@10: GEQContext *geq = inlink->dst->priv; yading@10: const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); yading@10: yading@10: geq->hsub = desc->log2_chroma_w; yading@10: geq->vsub = desc->log2_chroma_h; yading@10: geq->planes = desc->nb_components; yading@10: return 0; yading@10: } yading@10: yading@10: static int geq_filter_frame(AVFilterLink *inlink, AVFrame *in) yading@10: { yading@10: int plane; yading@10: GEQContext *geq = inlink->dst->priv; yading@10: AVFilterLink *outlink = inlink->dst->outputs[0]; yading@10: AVFrame *out; yading@10: double values[VAR_VARS_NB] = { yading@10: [VAR_N] = geq->framenum++, yading@10: [VAR_T] = in->pts == AV_NOPTS_VALUE ? NAN : in->pts * av_q2d(inlink->time_base), yading@10: }; yading@10: yading@10: geq->picref = in; yading@10: out = ff_get_video_buffer(outlink, outlink->w, outlink->h); yading@10: if (!out) { yading@10: av_frame_free(&in); yading@10: return AVERROR(ENOMEM); yading@10: } yading@10: av_frame_copy_props(out, in); yading@10: yading@10: for (plane = 0; plane < geq->planes && out->data[plane]; plane++) { yading@10: int x, y; yading@10: uint8_t *dst = out->data[plane]; yading@10: const int linesize = out->linesize[plane]; yading@10: const int w = inlink->w >> ((plane == 1 || plane == 2) ? geq->hsub : 0); yading@10: const int h = inlink->h >> ((plane == 1 || plane == 2) ? geq->vsub : 0); yading@10: yading@10: values[VAR_W] = w; yading@10: values[VAR_H] = h; yading@10: values[VAR_SW] = w / (double)inlink->w; yading@10: values[VAR_SH] = h / (double)inlink->h; yading@10: yading@10: for (y = 0; y < h; y++) { yading@10: values[VAR_Y] = y; yading@10: for (x = 0; x < w; x++) { yading@10: values[VAR_X] = x; yading@10: dst[x] = av_expr_eval(geq->e[plane], values, geq); yading@10: } yading@10: dst += linesize; yading@10: } yading@10: } yading@10: yading@10: av_frame_free(&geq->picref); yading@10: return ff_filter_frame(outlink, out); yading@10: } yading@10: yading@10: static av_cold void geq_uninit(AVFilterContext *ctx) yading@10: { yading@10: int i; yading@10: GEQContext *geq = ctx->priv; yading@10: yading@10: for (i = 0; i < FF_ARRAY_ELEMS(geq->e); i++) yading@10: av_expr_free(geq->e[i]); yading@10: } yading@10: yading@10: static const AVFilterPad geq_inputs[] = { yading@10: { yading@10: .name = "default", yading@10: .type = AVMEDIA_TYPE_VIDEO, yading@10: .config_props = geq_config_props, yading@10: .filter_frame = geq_filter_frame, yading@10: }, yading@10: { NULL } yading@10: }; yading@10: yading@10: static const AVFilterPad geq_outputs[] = { yading@10: { yading@10: .name = "default", yading@10: .type = AVMEDIA_TYPE_VIDEO, yading@10: }, yading@10: { NULL } yading@10: }; yading@10: yading@10: AVFilter avfilter_vf_geq = { yading@10: .name = "geq", yading@10: .description = NULL_IF_CONFIG_SMALL("Apply generic equation to each pixel."), yading@10: .priv_size = sizeof(GEQContext), yading@10: .init = geq_init, yading@10: .uninit = geq_uninit, yading@10: .query_formats = geq_query_formats, yading@10: .inputs = geq_inputs, yading@10: .outputs = geq_outputs, yading@10: .priv_class = &geq_class, yading@10: };