24eac3cff5
This fixes that the GIF encoder crashes with it because it has no palette. And the arguments for the pseudopalette apply to gray8 as much as to RGB8 etc. In addition the changes required in lavfi should be needed anyway when adding support for RGB8 etc. Signed-off-by: Reimar Döffinger <Reimar.Doeffinger@gmx.de>
381 lines
14 KiB
C
381 lines
14 KiB
C
/*
|
|
* Copyright (c) 2008 vmrsss
|
|
* Copyright (c) 2009 Stefano Sabatini
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* video padding filter
|
|
*/
|
|
|
|
#include "avfilter.h"
|
|
#include "libavutil/avstring.h"
|
|
#include "libavutil/eval.h"
|
|
#include "libavutil/pixdesc.h"
|
|
#include "libavutil/colorspace.h"
|
|
#include "libavutil/avassert.h"
|
|
#include "libavutil/imgutils.h"
|
|
#include "libavutil/parseutils.h"
|
|
#include "libavutil/mathematics.h"
|
|
#include "drawutils.h"
|
|
|
|
static const char *const var_names[] = {
|
|
"in_w", "iw",
|
|
"in_h", "ih",
|
|
"out_w", "ow",
|
|
"out_h", "oh",
|
|
"x",
|
|
"y",
|
|
"a",
|
|
"sar",
|
|
"dar",
|
|
"hsub",
|
|
"vsub",
|
|
NULL
|
|
};
|
|
|
|
enum var_name {
|
|
VAR_IN_W, VAR_IW,
|
|
VAR_IN_H, VAR_IH,
|
|
VAR_OUT_W, VAR_OW,
|
|
VAR_OUT_H, VAR_OH,
|
|
VAR_X,
|
|
VAR_Y,
|
|
VAR_A,
|
|
VAR_SAR,
|
|
VAR_DAR,
|
|
VAR_HSUB,
|
|
VAR_VSUB,
|
|
VARS_NB
|
|
};
|
|
|
|
static int query_formats(AVFilterContext *ctx)
|
|
{
|
|
avfilter_set_common_pixel_formats(ctx, ff_draw_supported_pixel_formats(0));
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
int w, h; ///< output dimensions, a value of 0 will result in the input size
|
|
int x, y; ///< offsets of the input area with respect to the padded area
|
|
int in_w, in_h; ///< width and height for the padded input video, which has to be aligned to the chroma values in order to avoid chroma issues
|
|
|
|
char w_expr[256]; ///< width expression string
|
|
char h_expr[256]; ///< height expression string
|
|
char x_expr[256]; ///< width expression string
|
|
char y_expr[256]; ///< height expression string
|
|
|
|
uint8_t rgba_color[4]; ///< color for the padding area
|
|
FFDrawContext draw;
|
|
FFDrawColor color;
|
|
int needs_copy;
|
|
} PadContext;
|
|
|
|
static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
|
|
{
|
|
PadContext *pad = ctx->priv;
|
|
char color_string[128] = "black";
|
|
|
|
av_strlcpy(pad->w_expr, "iw", sizeof(pad->w_expr));
|
|
av_strlcpy(pad->h_expr, "ih", sizeof(pad->h_expr));
|
|
av_strlcpy(pad->x_expr, "0" , sizeof(pad->w_expr));
|
|
av_strlcpy(pad->y_expr, "0" , sizeof(pad->h_expr));
|
|
|
|
if (args)
|
|
sscanf(args, "%255[^:]:%255[^:]:%255[^:]:%255[^:]:%127s",
|
|
pad->w_expr, pad->h_expr, pad->x_expr, pad->y_expr, color_string);
|
|
|
|
if (av_parse_color(pad->rgba_color, color_string, -1, ctx) < 0)
|
|
return AVERROR(EINVAL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int config_input(AVFilterLink *inlink)
|
|
{
|
|
AVFilterContext *ctx = inlink->dst;
|
|
PadContext *pad = ctx->priv;
|
|
int ret;
|
|
double var_values[VARS_NB], res;
|
|
char *expr;
|
|
|
|
ff_draw_init(&pad->draw, inlink->format, 0);
|
|
ff_draw_color(&pad->draw, &pad->color, pad->rgba_color);
|
|
|
|
var_values[VAR_IN_W] = var_values[VAR_IW] = inlink->w;
|
|
var_values[VAR_IN_H] = var_values[VAR_IH] = inlink->h;
|
|
var_values[VAR_OUT_W] = var_values[VAR_OW] = NAN;
|
|
var_values[VAR_OUT_H] = var_values[VAR_OH] = NAN;
|
|
var_values[VAR_A] = (float) inlink->w / inlink->h;
|
|
var_values[VAR_SAR] = inlink->sample_aspect_ratio.num ?
|
|
(float) inlink->sample_aspect_ratio.num / inlink->sample_aspect_ratio.den : 1;
|
|
var_values[VAR_DAR] = var_values[VAR_A] * var_values[VAR_SAR];
|
|
var_values[VAR_HSUB] = 1 << pad->draw.hsub_max;
|
|
var_values[VAR_VSUB] = 1 << pad->draw.vsub_max;
|
|
|
|
/* evaluate width and height */
|
|
av_expr_parse_and_eval(&res, (expr = pad->w_expr),
|
|
var_names, var_values,
|
|
NULL, NULL, NULL, NULL, NULL, 0, ctx);
|
|
pad->w = var_values[VAR_OUT_W] = var_values[VAR_OW] = res;
|
|
if ((ret = av_expr_parse_and_eval(&res, (expr = pad->h_expr),
|
|
var_names, var_values,
|
|
NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
|
|
goto eval_fail;
|
|
pad->h = var_values[VAR_OUT_H] = var_values[VAR_OH] = res;
|
|
/* evaluate the width again, as it may depend on the evaluated output height */
|
|
if ((ret = av_expr_parse_and_eval(&res, (expr = pad->w_expr),
|
|
var_names, var_values,
|
|
NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
|
|
goto eval_fail;
|
|
pad->w = var_values[VAR_OUT_W] = var_values[VAR_OW] = res;
|
|
|
|
/* evaluate x and y */
|
|
av_expr_parse_and_eval(&res, (expr = pad->x_expr),
|
|
var_names, var_values,
|
|
NULL, NULL, NULL, NULL, NULL, 0, ctx);
|
|
pad->x = var_values[VAR_X] = res;
|
|
if ((ret = av_expr_parse_and_eval(&res, (expr = pad->y_expr),
|
|
var_names, var_values,
|
|
NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
|
|
goto eval_fail;
|
|
pad->y = var_values[VAR_Y] = res;
|
|
/* evaluate x again, as it may depend on the evaluated y value */
|
|
if ((ret = av_expr_parse_and_eval(&res, (expr = pad->x_expr),
|
|
var_names, var_values,
|
|
NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
|
|
goto eval_fail;
|
|
pad->x = var_values[VAR_X] = res;
|
|
|
|
/* sanity check params */
|
|
if (pad->w < 0 || pad->h < 0 || pad->x < 0 || pad->y < 0) {
|
|
av_log(ctx, AV_LOG_ERROR, "Negative values are not acceptable.\n");
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
if (!pad->w)
|
|
pad->w = inlink->w;
|
|
if (!pad->h)
|
|
pad->h = inlink->h;
|
|
|
|
pad->w = ff_draw_round_to_sub(&pad->draw, 0, -1, pad->w);
|
|
pad->h = ff_draw_round_to_sub(&pad->draw, 1, -1, pad->h);
|
|
pad->x = ff_draw_round_to_sub(&pad->draw, 0, -1, pad->x);
|
|
pad->y = ff_draw_round_to_sub(&pad->draw, 1, -1, pad->y);
|
|
pad->in_w = ff_draw_round_to_sub(&pad->draw, 0, -1, inlink->w);
|
|
pad->in_h = ff_draw_round_to_sub(&pad->draw, 1, -1, inlink->h);
|
|
|
|
av_log(ctx, AV_LOG_INFO, "w:%d h:%d -> w:%d h:%d x:%d y:%d color:0x%02X%02X%02X%02X\n",
|
|
inlink->w, inlink->h, pad->w, pad->h, pad->x, pad->y,
|
|
pad->rgba_color[0], pad->rgba_color[1], pad->rgba_color[2], pad->rgba_color[3]);
|
|
|
|
if (pad->x < 0 || pad->y < 0 ||
|
|
pad->w <= 0 || pad->h <= 0 ||
|
|
(unsigned)pad->x + (unsigned)inlink->w > pad->w ||
|
|
(unsigned)pad->y + (unsigned)inlink->h > pad->h) {
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"Input area %d:%d:%d:%d not within the padded area 0:0:%d:%d or zero-sized\n",
|
|
pad->x, pad->y, pad->x + inlink->w, pad->y + inlink->h, pad->w, pad->h);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
return 0;
|
|
|
|
eval_fail:
|
|
av_log(NULL, AV_LOG_ERROR,
|
|
"Error when evaluating the expression '%s'\n", expr);
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int config_output(AVFilterLink *outlink)
|
|
{
|
|
PadContext *pad = outlink->src->priv;
|
|
|
|
outlink->w = pad->w;
|
|
outlink->h = pad->h;
|
|
return 0;
|
|
}
|
|
|
|
static AVFilterBufferRef *get_video_buffer(AVFilterLink *inlink, int perms, int w, int h)
|
|
{
|
|
PadContext *pad = inlink->dst->priv;
|
|
int align = (perms&AV_PERM_ALIGN) ? AVFILTER_ALIGN : 1;
|
|
|
|
AVFilterBufferRef *picref = avfilter_get_video_buffer(inlink->dst->outputs[0], perms,
|
|
w + (pad->w - pad->in_w) + 4*align,
|
|
h + (pad->h - pad->in_h));
|
|
int plane;
|
|
|
|
picref->video->w = w;
|
|
picref->video->h = h;
|
|
|
|
for (plane = 0; plane < 4 && picref->data[plane]; plane++)
|
|
picref->data[plane] += FFALIGN(pad->x >> pad->draw.hsub[plane], align) * pad->draw.pixelstep[plane] +
|
|
(pad->y >> pad->draw.vsub[plane]) * picref->linesize[plane];
|
|
|
|
return picref;
|
|
}
|
|
|
|
static int does_clip(PadContext *pad, AVFilterBufferRef *outpicref, int plane, int hsub, int vsub, int x, int y)
|
|
{
|
|
int64_t x_in_buf, y_in_buf;
|
|
|
|
x_in_buf = outpicref->data[plane] - outpicref->buf->data[plane]
|
|
+ (x >> hsub) * pad->draw.pixelstep[plane]
|
|
+ (y >> vsub) * outpicref->linesize[plane];
|
|
|
|
if(x_in_buf < 0 || x_in_buf % pad->draw.pixelstep[plane])
|
|
return 1;
|
|
x_in_buf /= pad->draw.pixelstep[plane];
|
|
|
|
av_assert0(outpicref->buf->linesize[plane]>0); //while reference can use negative linesize the main buffer should not
|
|
|
|
y_in_buf = x_in_buf / outpicref->buf->linesize[plane];
|
|
x_in_buf %= outpicref->buf->linesize[plane];
|
|
|
|
if( y_in_buf<<vsub >= outpicref->buf->h
|
|
|| x_in_buf<<hsub >= outpicref->buf->w)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void start_frame(AVFilterLink *inlink, AVFilterBufferRef *inpicref)
|
|
{
|
|
PadContext *pad = inlink->dst->priv;
|
|
AVFilterBufferRef *outpicref = avfilter_ref_buffer(inpicref, ~0);
|
|
int plane;
|
|
|
|
for (plane = 0; plane < 4 && outpicref->data[plane] && pad->draw.pixelstep[plane]; plane++) {
|
|
int hsub = pad->draw.hsub[plane];
|
|
int vsub = pad->draw.vsub[plane];
|
|
|
|
av_assert0(outpicref->buf->w>0 && outpicref->buf->h>0);
|
|
|
|
if(outpicref->format != outpicref->buf->format) //unsupported currently
|
|
break;
|
|
|
|
outpicref->data[plane] -= (pad->x >> hsub) * pad->draw.pixelstep[plane]
|
|
+ (pad->y >> vsub) * outpicref->linesize[plane];
|
|
|
|
if( does_clip(pad, outpicref, plane, hsub, vsub, 0, 0)
|
|
|| does_clip(pad, outpicref, plane, hsub, vsub, 0, pad->h-1)
|
|
|| does_clip(pad, outpicref, plane, hsub, vsub, pad->w-1, 0)
|
|
|| does_clip(pad, outpicref, plane, hsub, vsub, pad->w-1, pad->h-1)
|
|
)
|
|
break;
|
|
}
|
|
pad->needs_copy= plane < 4 && outpicref->data[plane];
|
|
if(pad->needs_copy){
|
|
av_log(inlink->dst, AV_LOG_DEBUG, "Direct padding impossible allocating new frame\n");
|
|
avfilter_unref_buffer(outpicref);
|
|
outpicref = avfilter_get_video_buffer(inlink->dst->outputs[0], AV_PERM_WRITE | AV_PERM_NEG_LINESIZES,
|
|
FFMAX(inlink->w, pad->w),
|
|
FFMAX(inlink->h, pad->h));
|
|
avfilter_copy_buffer_ref_props(outpicref, inpicref);
|
|
}
|
|
|
|
inlink->dst->outputs[0]->out_buf = outpicref;
|
|
|
|
outpicref->video->w = pad->w;
|
|
outpicref->video->h = pad->h;
|
|
|
|
avfilter_start_frame(inlink->dst->outputs[0], avfilter_ref_buffer(outpicref, ~0));
|
|
}
|
|
|
|
static void draw_send_bar_slice(AVFilterLink *link, int y, int h, int slice_dir, int before_slice)
|
|
{
|
|
PadContext *pad = link->dst->priv;
|
|
int bar_y, bar_h = 0;
|
|
|
|
if (slice_dir * before_slice == 1 && y == pad->y) {
|
|
/* top bar */
|
|
bar_y = 0;
|
|
bar_h = pad->y;
|
|
} else if (slice_dir * before_slice == -1 && (y + h) == (pad->y + pad->in_h)) {
|
|
/* bottom bar */
|
|
bar_y = pad->y + pad->in_h;
|
|
bar_h = pad->h - pad->in_h - pad->y;
|
|
}
|
|
|
|
if (bar_h) {
|
|
ff_fill_rectangle(&pad->draw, &pad->color,
|
|
link->dst->outputs[0]->out_buf->data,
|
|
link->dst->outputs[0]->out_buf->linesize,
|
|
0, bar_y, pad->w, bar_h);
|
|
avfilter_draw_slice(link->dst->outputs[0], bar_y, bar_h, slice_dir);
|
|
}
|
|
}
|
|
|
|
static void draw_slice(AVFilterLink *link, int y, int h, int slice_dir)
|
|
{
|
|
PadContext *pad = link->dst->priv;
|
|
AVFilterBufferRef *outpic = link->dst->outputs[0]->out_buf;
|
|
AVFilterBufferRef *inpic = link->cur_buf;
|
|
|
|
y += pad->y;
|
|
|
|
y = ff_draw_round_to_sub(&pad->draw, 1, -1, y);
|
|
h = ff_draw_round_to_sub(&pad->draw, 1, -1, h);
|
|
|
|
if (!h)
|
|
return;
|
|
draw_send_bar_slice(link, y, h, slice_dir, 1);
|
|
|
|
/* left border */
|
|
ff_fill_rectangle(&pad->draw, &pad->color, outpic->data, outpic->linesize,
|
|
0, y, pad->x, h);
|
|
|
|
if(pad->needs_copy){
|
|
ff_copy_rectangle2(&pad->draw,
|
|
outpic->data, outpic->linesize,
|
|
inpic ->data, inpic ->linesize,
|
|
pad->x, y, 0, y - pad->y, inpic->video->w, h);
|
|
}
|
|
|
|
/* right border */
|
|
ff_fill_rectangle(&pad->draw, &pad->color, outpic->data, outpic->linesize,
|
|
pad->x + pad->in_w, y, pad->w - pad->x - pad->in_w, h);
|
|
avfilter_draw_slice(link->dst->outputs[0], y, h, slice_dir);
|
|
|
|
draw_send_bar_slice(link, y, h, slice_dir, -1);
|
|
}
|
|
|
|
AVFilter avfilter_vf_pad = {
|
|
.name = "pad",
|
|
.description = NULL_IF_CONFIG_SMALL("Pad input image to width:height[:x:y[:color]] (default x and y: 0, default color: black)."),
|
|
|
|
.priv_size = sizeof(PadContext),
|
|
.init = init,
|
|
.query_formats = query_formats,
|
|
|
|
.inputs = (const AVFilterPad[]) {{ .name = "default",
|
|
.type = AVMEDIA_TYPE_VIDEO,
|
|
.config_props = config_input,
|
|
.get_video_buffer = get_video_buffer,
|
|
.start_frame = start_frame,
|
|
.draw_slice = draw_slice, },
|
|
{ .name = NULL}},
|
|
|
|
.outputs = (const AVFilterPad[]) {{ .name = "default",
|
|
.type = AVMEDIA_TYPE_VIDEO,
|
|
.config_props = config_output, },
|
|
{ .name = NULL}},
|
|
};
|