1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2024-12-23 12:43:46 +02:00

avfilter: add fillborders filter

Signed-off-by: Paul B Mahol <onemda@gmail.com>
This commit is contained in:
Paul B Mahol 2017-11-18 18:18:39 +01:00
parent 95bacb521a
commit e1dd97bd4c
6 changed files with 440 additions and 1 deletions

View File

@ -23,6 +23,7 @@ version <next>:
- audio lv2 wrapper filter
- VAAPI VP8 decoding
- AMD AMF H.264 and HEVC encoders
- video fillborders filter
version 3.4:

View File

@ -8610,6 +8610,48 @@ framework.
It does not take parameters.
@section fillborders
Fill borders of the input video, without changing video stream dimensions.
Sometimes video can have garbage at the four edges and you may not want to
crop video input to keep size multiple of some number.
This filter accepts the following options:
@table @option
@item left
Number of pixels to fill from left border.
@item right
Number of pixels to fill from right border.
@item top
Number of pixels to fill from top border.
@item bottom
Number of pixels to fill from bottom border.
@item mode
Set fill mode.
It accepts the following values:
@table @samp
@item smear
fill pixels using outermost pixels
@item mirror
fill pixels using mirroring
@item fixed
fill pixels with constant value
@end table
Default is @var{smear}.
@item color
Set color for pixels in fixed mode. Default is @var{black}.
@end table
@section find_rect
Find a rectangular object

View File

@ -192,6 +192,7 @@ OBJS-$(CONFIG_FIELD_FILTER) += vf_field.o
OBJS-$(CONFIG_FIELDHINT_FILTER) += vf_fieldhint.o
OBJS-$(CONFIG_FIELDMATCH_FILTER) += vf_fieldmatch.o
OBJS-$(CONFIG_FIELDORDER_FILTER) += vf_fieldorder.o
OBJS-$(CONFIG_FILLBORDERS_FILTER) += vf_fillborders.o
OBJS-$(CONFIG_FIND_RECT_FILTER) += vf_find_rect.o lavfutils.o
OBJS-$(CONFIG_FLOODFILL_FILTER) += vf_floodfill.o
OBJS-$(CONFIG_FORMAT_FILTER) += vf_format.o

View File

@ -202,6 +202,7 @@ static void register_all(void)
REGISTER_FILTER(FIELDHINT, fieldhint, vf);
REGISTER_FILTER(FIELDMATCH, fieldmatch, vf);
REGISTER_FILTER(FIELDORDER, fieldorder, vf);
REGISTER_FILTER(FILLBORDERS, fillborders, vf);
REGISTER_FILTER(FIND_RECT, find_rect, vf);
REGISTER_FILTER(FLOODFILL, floodfill, vf);
REGISTER_FILTER(FORMAT, format, vf);

View File

@ -30,7 +30,7 @@
#include "libavutil/version.h"
#define LIBAVFILTER_VERSION_MAJOR 7
#define LIBAVFILTER_VERSION_MINOR 5
#define LIBAVFILTER_VERSION_MINOR 6
#define LIBAVFILTER_VERSION_MICRO 100
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \

View File

@ -0,0 +1,394 @@
/*
* Copyright (c) 2017 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 "libavutil/colorspace.h"
#include "libavutil/common.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "avfilter.h"
#include "drawutils.h"
#include "formats.h"
#include "internal.h"
#include "video.h"
enum { Y, U, V, A };
enum { R, G, B };
enum FillMode { FM_SMEAR, FM_MIRROR, FM_FIXED, FM_NB_MODES };
typedef struct Borders {
int left, right, top, bottom;
} Borders;
typedef struct FillBordersContext {
const AVClass *class;
int left, right, top, bottom;
int mode;
int nb_planes;
int depth;
Borders borders[4];
int planewidth[4];
int planeheight[4];
uint8_t fill[4];
uint8_t yuv_color[4];
uint8_t rgba_color[4];
void (*fillborders)(struct FillBordersContext *s, AVFrame *frame);
} FillBordersContext;
static int query_formats(AVFilterContext *ctx)
{
static const enum AVPixelFormat pix_fmts[] = {
AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P,
AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P,
AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
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_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12,
AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14,
AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10,
AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY16,
AV_PIX_FMT_NONE
};
AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
if (!fmts_list)
return AVERROR(ENOMEM);
return ff_set_common_formats(ctx, fmts_list);
}
static void smear_borders8(FillBordersContext *s, AVFrame *frame)
{
int p, y;
for (p = 0; p < s->nb_planes; p++) {
uint8_t *ptr = frame->data[p];
int linesize = frame->linesize[p];
for (y = s->borders[p].top; y < s->planeheight[p] - s->borders[p].bottom; y++) {
memset(ptr + y * linesize,
*(ptr + y * linesize + s->borders[p].left),
s->borders[p].left);
memset(ptr + y * linesize + s->planewidth[p] - s->borders[p].right,
*(ptr + y * linesize + s->planewidth[p] - s->borders[p].right - 1),
s->borders[p].right);
}
for (y = 0; y < s->borders[p].top; y++) {
memcpy(ptr + y * linesize,
ptr + s->borders[p].top * linesize, s->planewidth[p]);
}
for (y = s->planeheight[p] - s->borders[p].bottom; y < s->planeheight[p]; y++) {
memcpy(ptr + y * linesize,
ptr + (s->planeheight[p] - s->borders[p].bottom - 1) * linesize,
s->planewidth[p]);
}
}
}
static void smear_borders16(FillBordersContext *s, AVFrame *frame)
{
int p, y, x;
for (p = 0; p < s->nb_planes; p++) {
uint16_t *ptr = (uint16_t *)frame->data[p];
int linesize = frame->linesize[p] / 2;
for (y = s->borders[p].top; y < s->planeheight[p] - s->borders[p].bottom; y++) {
for (x = 0; x < s->borders[p].left; x++) {
ptr[y * linesize + x] = *(ptr + y * linesize + s->borders[p].left);
}
for (x = 0; x < s->borders[p].right; x++) {
ptr[y * linesize + s->planewidth[p] - s->borders[p].right + x] =
*(ptr + y * linesize + s->planewidth[p] - s->borders[p].right - 1);
}
}
for (y = 0; y < s->borders[p].top; y++) {
memcpy(ptr + y * linesize,
ptr + s->borders[p].top * linesize, s->planewidth[p] * 2);
}
for (y = s->planeheight[p] - s->borders[p].bottom; y < s->planeheight[p]; y++) {
memcpy(ptr + y * linesize,
ptr + (s->planeheight[p] - s->borders[p].bottom - 1) * linesize,
s->planewidth[p] * 2);
}
}
}
static void mirror_borders8(FillBordersContext *s, AVFrame *frame)
{
int p, y, x;
for (p = 0; p < s->nb_planes; p++) {
uint8_t *ptr = frame->data[p];
int linesize = frame->linesize[p];
for (y = s->borders[p].top; y < s->planeheight[p] - s->borders[p].bottom; y++) {
for (x = 0; x < s->borders[p].left; x++) {
ptr[y * linesize + x] = ptr[y * linesize + s->borders[p].left * 2 - 1 - x];
}
for (x = 0; x < s->borders[p].right; x++) {
ptr[y * linesize + s->planewidth[p] - s->borders[p].right + x] =
ptr[y * linesize + s->planewidth[p] - s->borders[p].right - 1 - x];
}
}
for (y = 0; y < s->borders[p].top; y++) {
memcpy(ptr + y * linesize,
ptr + (s->borders[p].top * 2 - 1 - y) * linesize,
s->planewidth[p]);
}
for (y = 0; y < s->borders[p].bottom; y++) {
memcpy(ptr + (s->planeheight[p] - s->borders[p].bottom + y) * linesize,
ptr + (s->planeheight[p] - s->borders[p].bottom - 1 - y) * linesize,
s->planewidth[p]);
}
}
}
static void mirror_borders16(FillBordersContext *s, AVFrame *frame)
{
int p, y, x;
for (p = 0; p < s->nb_planes; p++) {
uint16_t *ptr = (uint16_t *)frame->data[p];
int linesize = frame->linesize[p] / 2;
for (y = s->borders[p].top; y < s->planeheight[p] - s->borders[p].bottom; y++) {
for (x = 0; x < s->borders[p].left; x++) {
ptr[y * linesize + x] = ptr[y * linesize + s->borders[p].left * 2 - 1 - x];
}
for (x = 0; x < s->borders[p].right; x++) {
ptr[y * linesize + s->planewidth[p] - s->borders[p].right + x] =
ptr[y * linesize + s->planewidth[p] - s->borders[p].right - 1 - x];
}
}
for (y = 0; y < s->borders[p].top; y++) {
memcpy(ptr + y * linesize,
ptr + (s->borders[p].top * 2 - 1 - y) * linesize,
s->planewidth[p] * 2);
}
for (y = 0; y < s->borders[p].bottom; y++) {
memcpy(ptr + (s->planeheight[p] - s->borders[p].bottom + y) * linesize,
ptr + (s->planeheight[p] - s->borders[p].bottom - 1 - y) * linesize,
s->planewidth[p] * 2);
}
}
}
static void fixed_borders8(FillBordersContext *s, AVFrame *frame)
{
int p, y;
for (p = 0; p < s->nb_planes; p++) {
uint8_t *ptr = frame->data[p];
uint8_t fill = s->fill[p];
int linesize = frame->linesize[p];
for (y = s->borders[p].top; y < s->planeheight[p] - s->borders[p].bottom; y++) {
memset(ptr + y * linesize, fill, s->borders[p].left);
memset(ptr + y * linesize + s->planewidth[p] - s->borders[p].right, fill,
s->borders[p].right);
}
for (y = 0; y < s->borders[p].top; y++) {
memset(ptr + y * linesize, fill, s->planewidth[p]);
}
for (y = s->planeheight[p] - s->borders[p].bottom; y < s->planeheight[p]; y++) {
memset(ptr + y * linesize, fill, s->planewidth[p]);
}
}
}
static void fixed_borders16(FillBordersContext *s, AVFrame *frame)
{
int p, y, x;
for (p = 0; p < s->nb_planes; p++) {
uint16_t *ptr = (uint16_t *)frame->data[p];
uint16_t fill = s->fill[p] << (s->depth - 8);
int linesize = frame->linesize[p] / 2;
for (y = s->borders[p].top; y < s->planeheight[p] - s->borders[p].bottom; y++) {
for (x = 0; x < s->borders[p].left; x++) {
ptr[y * linesize + x] = fill;
}
for (x = 0; x < s->borders[p].right; x++) {
ptr[y * linesize + s->planewidth[p] - s->borders[p].right + x] = fill;
}
}
for (y = 0; y < s->borders[p].top; y++) {
for (x = 0; x < s->planewidth[p]; x++) {
ptr[y * linesize + x] = fill;
}
}
for (y = s->planeheight[p] - s->borders[p].bottom; y < s->planeheight[p]; y++) {
for (x = 0; x < s->planewidth[p]; x++) {
ptr[y * linesize + x] = fill;
}
}
}
}
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
{
FillBordersContext *s = inlink->dst->priv;
s->fillborders(s, frame);
return ff_filter_frame(inlink->dst->outputs[0], frame);
}
static int config_input(AVFilterLink *inlink)
{
AVFilterContext *ctx = inlink->dst;
FillBordersContext *s = ctx->priv;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
s->nb_planes = desc->nb_components;
s->depth = desc->comp[0].depth;
s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
s->planeheight[0] = s->planeheight[3] = inlink->h;
s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
s->planewidth[0] = s->planewidth[3] = inlink->w;
s->borders[0].left = s->borders[3].left = s->left;
s->borders[0].right = s->borders[3].right = s->right;
s->borders[0].top = s->borders[3].top = s->top;
s->borders[0].bottom = s->borders[3].bottom = s->bottom;
s->borders[1].left = s->left >> desc->log2_chroma_w;
s->borders[1].right = s->right >> desc->log2_chroma_w;
s->borders[1].top = s->top >> desc->log2_chroma_h;
s->borders[1].bottom = s->bottom >> desc->log2_chroma_h;
s->borders[2].left = s->left >> desc->log2_chroma_w;
s->borders[2].right = s->right >> desc->log2_chroma_w;
s->borders[2].top = s->top >> desc->log2_chroma_h;
s->borders[2].bottom = s->bottom >> desc->log2_chroma_h;
if (inlink->w < s->left + s->right ||
inlink->w <= s->left ||
inlink->w <= s->right ||
inlink->h < s->top + s->bottom ||
inlink->h <= s->top ||
inlink->h <= s->bottom ||
inlink->w < s->left * 2 ||
inlink->w < s->right * 2 ||
inlink->h < s->top * 2 ||
inlink->h < s->bottom * 2) {
av_log(ctx, AV_LOG_ERROR, "Borders are bigger than input frame size.\n");
return AVERROR(EINVAL);
}
switch (s->mode) {
case FM_SMEAR: s->fillborders = s->depth <= 8 ? smear_borders8 : smear_borders16; break;
case FM_MIRROR: s->fillborders = s->depth <= 8 ? mirror_borders8 : mirror_borders16; break;
case FM_FIXED: s->fillborders = s->depth <= 8 ? fixed_borders8 : fixed_borders16; break;
}
s->yuv_color[Y] = RGB_TO_Y_CCIR(s->rgba_color[R], s->rgba_color[G], s->rgba_color[B]);
s->yuv_color[U] = RGB_TO_U_CCIR(s->rgba_color[R], s->rgba_color[G], s->rgba_color[B], 0);
s->yuv_color[V] = RGB_TO_V_CCIR(s->rgba_color[R], s->rgba_color[G], s->rgba_color[B], 0);
s->yuv_color[A] = s->rgba_color[A];
if (desc->flags & AV_PIX_FMT_FLAG_RGB) {
uint8_t rgba_map[4];
int i;
ff_fill_rgba_map(rgba_map, inlink->format);
for (i = 0; i < 4; i++)
s->fill[rgba_map[i]] = s->rgba_color[i];
} else {
memcpy(s->fill, s->yuv_color, sizeof(s->yuv_color));
}
return 0;
}
#define OFFSET(x) offsetof(FillBordersContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
static const AVOption fillborders_options[] = {
{ "left", "set the left fill border", OFFSET(left), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
{ "right", "set the right fill border", OFFSET(right), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
{ "top", "set the top fill border", OFFSET(top), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
{ "bottom", "set the bottom fill border", OFFSET(bottom), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
{ "mode", "set the fill borders mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=FM_SMEAR}, 0, FM_NB_MODES-1, FLAGS, "mode" },
{ "smear", NULL, 0, AV_OPT_TYPE_CONST, {.i64=FM_SMEAR}, 0, 0, FLAGS, "mode" },
{ "mirror", NULL, 0, AV_OPT_TYPE_CONST, {.i64=FM_MIRROR}, 0, 0, FLAGS, "mode" },
{ "fixed", NULL, 0, AV_OPT_TYPE_CONST, {.i64=FM_FIXED}, 0, 0, FLAGS, "mode" },
{ "color", "set the color for the fixed mode", OFFSET(rgba_color), AV_OPT_TYPE_COLOR, {.str = "black"}, .flags = FLAGS },
{ NULL }
};
AVFILTER_DEFINE_CLASS(fillborders);
static const AVFilterPad fillborders_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.config_props = config_input,
.filter_frame = filter_frame,
.needs_writable = 1,
},
{ NULL }
};
static const AVFilterPad fillborders_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
},
{ NULL }
};
AVFilter ff_vf_fillborders = {
.name = "fillborders",
.description = NULL_IF_CONFIG_SMALL("Fill borders of the input video."),
.priv_size = sizeof(FillBordersContext),
.priv_class = &fillborders_class,
.query_formats = query_formats,
.inputs = fillborders_inputs,
.outputs = fillborders_outputs,
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
};