|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "libavutil/imgutils.h" |
|
#include "libavutil/opt.h" |
|
#include "filters.h" |
|
#include "internal.h" |
|
#include "video.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 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 ret, 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); |
|
|
|
ret = ff_inlink_make_frame_writable(inlink, &in); |
|
if (ret < 0) { |
|
av_frame_free(&in); |
|
return ret; |
|
} |
|
|
|
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, |
|
}, |
|
}; |
|
|
|
const 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, |
|
FILTER_INPUTS(cover_rect_inputs), |
|
FILTER_OUTPUTS(ff_video_default_filterpad), |
|
FILTER_PIXFMTS(AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVJ420P), |
|
.priv_class = &cover_rect_class, |
|
}; |
|
|