/* * Copyright (c) 2014-2015 Michael Niedermayer * * This file is part of FFmpeg. * * FFmpeg 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. * * 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 General Public License for more details. * * You should have received a copy of the GNU 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. */ /** * @todo switch to dualinput */ #include "libavutil/avassert.h" #include "libavutil/imgutils.h" #include "libavutil/opt.h" #include "internal.h" #include "lavfutils.h" enum mode { MODE_COVER, MODE_BLUR, NB_MODES }; typedef struct CoverContext { AVClass *class; int mode; char *cover_filename; AVFrame *cover_frame; int width, height; } CoverContext; #define OFFSET(x) offsetof(CoverContext, x) #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM static const AVOption cover_rect_options[] = { { "cover", "cover bitmap filename", OFFSET(cover_filename), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = FLAGS }, { "mode", "set removal mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64 = MODE_BLUR}, 0, NB_MODES - 1, FLAGS, "mode" }, { "cover", "cover area with bitmap", 0, AV_OPT_TYPE_CONST, {.i64 = MODE_COVER}, INT_MIN, INT_MAX, FLAGS, "mode" }, { "blur", "blur area", 0, AV_OPT_TYPE_CONST, {.i64 = MODE_BLUR}, INT_MIN, INT_MAX, FLAGS, "mode" }, { NULL } }; AVFILTER_DEFINE_CLASS(cover_rect); static int query_formats(AVFilterContext *ctx) { static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_NONE }; return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); } static int config_input(AVFilterLink *inlink) { return 0; } static void cover_rect(CoverContext *cover, AVFrame *in, int offx, int offy) { int x, y, p; for (p = 0; p < 3; p++) { uint8_t *data = in->data[p] + (offx>>!!p) + (offy>>!!p) * in->linesize[p]; const uint8_t *src = cover->cover_frame->data[p]; int w = AV_CEIL_RSHIFT(cover->cover_frame->width , !!p); int h = AV_CEIL_RSHIFT(cover->cover_frame->height, !!p); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { data[x] = src[x]; } data += in->linesize[p]; src += cover->cover_frame->linesize[p]; } } } static void blur(CoverContext *cover, AVFrame *in, int offx, int offy) { int x, y, p; for (p=0; p<3; p++) { int ox = offx>>!!p; int oy = offy>>!!p; int stride = in->linesize[p]; uint8_t *data = in->data[p] + ox + oy * stride; int w = AV_CEIL_RSHIFT(cover->width , !!p); int h = AV_CEIL_RSHIFT(cover->height, !!p); int iw = AV_CEIL_RSHIFT(in->width , !!p); int ih = AV_CEIL_RSHIFT(in->height, !!p); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { int c = 0; int s = 0; if (ox) { int scale = 65536 / (x + 1); s += data[-1 + y*stride] * scale; c += scale; } if (oy) { int scale = 65536 / (y + 1); s += data[x - stride] * scale; c += scale; } if (ox + w < iw) { int scale = 65536 / (w - x); s += data[w + y*stride] * scale; c += scale; } if (oy + h < ih) { int scale = 65536 / (h - y); s += data[x + h*stride] * scale; c += scale; } data[x + y*stride] = c ? (s + (c>>1)) / c : 0; } } } } static int filter_frame(AVFilterLink *inlink, AVFrame *in) { AVFilterContext *ctx = inlink->dst; CoverContext *cover = ctx->priv; AVDictionaryEntry *ex, *ey, *ew, *eh; int x = -1, y = -1, w = -1, h = -1; char *xendptr = NULL, *yendptr = NULL, *wendptr = NULL, *hendptr = NULL; ex = av_dict_get(in->metadata, "lavfi.rect.x", NULL, AV_DICT_MATCH_CASE); ey = av_dict_get(in->metadata, "lavfi.rect.y", NULL, AV_DICT_MATCH_CASE); ew = av_dict_get(in->metadata, "lavfi.rect.w", NULL, AV_DICT_MATCH_CASE); eh = av_dict_get(in->metadata, "lavfi.rect.h", NULL, AV_DICT_MATCH_CASE); if (ex && ey && ew && eh) { x = strtol(ex->value, &xendptr, 10); y = strtol(ey->value, ¥dptr, 10); w = strtol(ew->value, &wendptr, 10); h = strtol(eh->value, &hendptr, 10); } if (!xendptr || *xendptr || !yendptr || *yendptr || !wendptr || *wendptr || !hendptr || *hendptr ) { return ff_filter_frame(ctx->outputs[0], in); } if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } w = FFMIN(w, in->width - x); h = FFMIN(h, in->height - y); if (w > in->width || h > in->height || w <= 0 || h <= 0) return AVERROR(EINVAL); if (cover->cover_frame) { if (w != cover->cover_frame->width || h != cover->cover_frame->height) return AVERROR(EINVAL); } cover->width = w; cover->height = h; x = av_clip(x, 0, in->width - w); y = av_clip(y, 0, in->height - h); av_frame_make_writable(in); if (cover->mode == MODE_BLUR) { blur (cover, in, x, y); } else { cover_rect(cover, in, x, y); } return ff_filter_frame(ctx->outputs[0], in); } static av_cold void uninit(AVFilterContext *ctx) { CoverContext *cover = ctx->priv; if (cover->cover_frame) av_freep(&cover->cover_frame->data[0]); av_frame_free(&cover->cover_frame); } static av_cold int init(AVFilterContext *ctx) { CoverContext *cover = ctx->priv; int ret; if (cover->mode == MODE_COVER) { if (!cover->cover_filename) { av_log(ctx, AV_LOG_ERROR, "cover filename not set\n"); return AVERROR(EINVAL); } cover->cover_frame = av_frame_alloc(); if (!cover->cover_frame) return AVERROR(ENOMEM); if ((ret = ff_load_image(cover->cover_frame->data, cover->cover_frame->linesize, &cover->cover_frame->width, &cover->cover_frame->height, &cover->cover_frame->format, cover->cover_filename, ctx)) < 0) return ret; if (cover->cover_frame->format != AV_PIX_FMT_YUV420P && cover->cover_frame->format != AV_PIX_FMT_YUVJ420P) { av_log(ctx, AV_LOG_ERROR, "cover image is not a YUV420 image\n"); return AVERROR(EINVAL); } } return 0; } static const AVFilterPad cover_rect_inputs[] = { { .name = "default", .type = AVMEDIA_TYPE_VIDEO, .config_props = config_input, .filter_frame = filter_frame, }, { NULL } }; static const AVFilterPad cover_rect_outputs[] = { { .name = "default", .type = AVMEDIA_TYPE_VIDEO, }, { NULL } }; AVFilter ff_vf_cover_rect = { .name = "cover_rect", .description = NULL_IF_CONFIG_SMALL("Find and cover a user specified object."), .priv_size = sizeof(CoverContext), .init = init, .uninit = uninit, .query_formats = query_formats, .inputs = cover_rect_inputs, .outputs = cover_rect_outputs, .priv_class = &cover_rect_class, };