2 * Copyright (c) 2008 vmrsss
3 * Copyright (c) 2009 Stefano Sabatini
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 * video padding filter
31 #include "libavutil/avstring.h"
32 #include "libavutil/common.h"
33 #include "libavutil/eval.h"
34 #include "libavutil/pixdesc.h"
35 #include "libavutil/colorspace.h"
36 #include "libavutil/avassert.h"
37 #include "libavutil/imgutils.h"
38 #include "libavutil/parseutils.h"
39 #include "libavutil/mathematics.h"
40 #include "libavutil/opt.h"
42 #include "drawutils.h"
44 static const char *const var_names
[] = {
74 static int query_formats(AVFilterContext
*ctx
)
76 ff_set_common_formats(ctx
, ff_draw_supported_pixel_formats(0));
80 typedef struct PadContext
{
82 int w
, h
; ///< output dimensions, a value of 0 will result in the input size
83 int x
, y
; ///< offsets of the input area with respect to the padded area
84 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
86 char *w_expr
; ///< width expression string
87 char *h_expr
; ///< height expression string
88 char *x_expr
; ///< width expression string
89 char *y_expr
; ///< height expression string
90 uint8_t rgba_color
[4]; ///< color for the padding area
95 static int config_input(AVFilterLink
*inlink
)
97 AVFilterContext
*ctx
= inlink
->dst
;
98 PadContext
*s
= ctx
->priv
;
100 double var_values
[VARS_NB
], res
;
103 ff_draw_init(&s
->draw
, inlink
->format
, 0);
104 ff_draw_color(&s
->draw
, &s
->color
, s
->rgba_color
);
106 var_values
[VAR_IN_W
] = var_values
[VAR_IW
] = inlink
->w
;
107 var_values
[VAR_IN_H
] = var_values
[VAR_IH
] = inlink
->h
;
108 var_values
[VAR_OUT_W
] = var_values
[VAR_OW
] = NAN
;
109 var_values
[VAR_OUT_H
] = var_values
[VAR_OH
] = NAN
;
110 var_values
[VAR_A
] = (double) inlink
->w
/ inlink
->h
;
111 var_values
[VAR_SAR
] = inlink
->sample_aspect_ratio
.num
?
112 (double) inlink
->sample_aspect_ratio
.num
/ inlink
->sample_aspect_ratio
.den
: 1;
113 var_values
[VAR_DAR
] = var_values
[VAR_A
] * var_values
[VAR_SAR
];
114 var_values
[VAR_HSUB
] = 1 << s
->draw
.hsub_max
;
115 var_values
[VAR_VSUB
] = 1 << s
->draw
.vsub_max
;
117 /* evaluate width and height */
118 av_expr_parse_and_eval(&res
, (expr
= s
->w_expr
),
119 var_names
, var_values
,
120 NULL
, NULL
, NULL
, NULL
, NULL
, 0, ctx
);
121 s
->w
= var_values
[VAR_OUT_W
] = var_values
[VAR_OW
] = res
;
122 if ((ret
= av_expr_parse_and_eval(&res
, (expr
= s
->h_expr
),
123 var_names
, var_values
,
124 NULL
, NULL
, NULL
, NULL
, NULL
, 0, ctx
)) < 0)
126 s
->h
= var_values
[VAR_OUT_H
] = var_values
[VAR_OH
] = res
;
127 /* evaluate the width again, as it may depend on the evaluated output height */
128 if ((ret
= av_expr_parse_and_eval(&res
, (expr
= s
->w_expr
),
129 var_names
, var_values
,
130 NULL
, NULL
, NULL
, NULL
, NULL
, 0, ctx
)) < 0)
132 s
->w
= var_values
[VAR_OUT_W
] = var_values
[VAR_OW
] = res
;
134 /* evaluate x and y */
135 av_expr_parse_and_eval(&res
, (expr
= s
->x_expr
),
136 var_names
, var_values
,
137 NULL
, NULL
, NULL
, NULL
, NULL
, 0, ctx
);
138 s
->x
= var_values
[VAR_X
] = res
;
139 if ((ret
= av_expr_parse_and_eval(&res
, (expr
= s
->y_expr
),
140 var_names
, var_values
,
141 NULL
, NULL
, NULL
, NULL
, NULL
, 0, ctx
)) < 0)
143 s
->y
= var_values
[VAR_Y
] = res
;
144 /* evaluate x again, as it may depend on the evaluated y value */
145 if ((ret
= av_expr_parse_and_eval(&res
, (expr
= s
->x_expr
),
146 var_names
, var_values
,
147 NULL
, NULL
, NULL
, NULL
, NULL
, 0, ctx
)) < 0)
149 s
->x
= var_values
[VAR_X
] = res
;
151 /* sanity check params */
152 if (s
->w
< 0 || s
->h
< 0 || s
->x
< 0 || s
->y
< 0) {
153 av_log(ctx
, AV_LOG_ERROR
, "Negative values are not acceptable.\n");
154 return AVERROR(EINVAL
);
162 s
->w
= ff_draw_round_to_sub(&s
->draw
, 0, -1, s
->w
);
163 s
->h
= ff_draw_round_to_sub(&s
->draw
, 1, -1, s
->h
);
164 s
->x
= ff_draw_round_to_sub(&s
->draw
, 0, -1, s
->x
);
165 s
->y
= ff_draw_round_to_sub(&s
->draw
, 1, -1, s
->y
);
166 s
->in_w
= ff_draw_round_to_sub(&s
->draw
, 0, -1, inlink
->w
);
167 s
->in_h
= ff_draw_round_to_sub(&s
->draw
, 1, -1, inlink
->h
);
169 av_log(ctx
, AV_LOG_VERBOSE
, "w:%d h:%d -> w:%d h:%d x:%d y:%d color:0x%02X%02X%02X%02X\n",
170 inlink
->w
, inlink
->h
, s
->w
, s
->h
, s
->x
, s
->y
,
171 s
->rgba_color
[0], s
->rgba_color
[1], s
->rgba_color
[2], s
->rgba_color
[3]);
173 if (s
->x
< 0 || s
->y
< 0 ||
174 s
->w
<= 0 || s
->h
<= 0 ||
175 (unsigned)s
->x
+ (unsigned)inlink
->w
> s
->w
||
176 (unsigned)s
->y
+ (unsigned)inlink
->h
> s
->h
) {
177 av_log(ctx
, AV_LOG_ERROR
,
178 "Input area %d:%d:%d:%d not within the padded area 0:0:%d:%d or zero-sized\n",
179 s
->x
, s
->y
, s
->x
+ inlink
->w
, s
->y
+ inlink
->h
, s
->w
, s
->h
);
180 return AVERROR(EINVAL
);
186 av_log(NULL
, AV_LOG_ERROR
,
187 "Error when evaluating the expression '%s'\n", expr
);
192 static int config_output(AVFilterLink
*outlink
)
194 PadContext
*s
= outlink
->src
->priv
;
201 static AVFrame
*get_video_buffer(AVFilterLink
*inlink
, int w
, int h
)
203 PadContext
*s
= inlink
->dst
->priv
;
205 AVFrame
*frame
= ff_get_video_buffer(inlink
->dst
->outputs
[0],
206 w
+ (s
->w
- s
->in_w
),
207 h
+ (s
->h
- s
->in_h
));
216 for (plane
= 0; plane
< 4 && frame
->data
[plane
] && frame
->linesize
[plane
]; plane
++) {
217 int hsub
= s
->draw
.hsub
[plane
];
218 int vsub
= s
->draw
.vsub
[plane
];
219 frame
->data
[plane
] += (s
->x
>> hsub
) * s
->draw
.pixelstep
[plane
] +
220 (s
->y
>> vsub
) * frame
->linesize
[plane
];
226 /* check whether each plane in this buffer can be padded without copying */
227 static int buffer_needs_copy(PadContext
*s
, AVFrame
*frame
, AVBufferRef
*buf
)
229 int planes
[4] = { -1, -1, -1, -1}, *p
= planes
;
232 /* get all planes in this buffer */
233 for (i
= 0; i
< FF_ARRAY_ELEMS(planes
) && frame
->data
[i
]; i
++) {
234 if (av_frame_get_plane_buffer(frame
, i
) == buf
)
238 /* for each plane in this buffer, check that it can be padded without
239 * going over buffer bounds or other planes */
240 for (i
= 0; i
< FF_ARRAY_ELEMS(planes
) && planes
[i
] >= 0; i
++) {
241 int hsub
= s
->draw
.hsub
[planes
[i
]];
242 int vsub
= s
->draw
.vsub
[planes
[i
]];
244 uint8_t *start
= frame
->data
[planes
[i
]];
245 uint8_t *end
= start
+ (frame
->height
>> vsub
) *
246 frame
->linesize
[planes
[i
]];
248 /* amount of free space needed before the start and after the end
250 ptrdiff_t req_start
= (s
->x
>> hsub
) * s
->draw
.pixelstep
[planes
[i
]] +
251 (s
->y
>> vsub
) * frame
->linesize
[planes
[i
]];
252 ptrdiff_t req_end
= ((s
->w
- s
->x
- frame
->width
) >> hsub
) *
253 s
->draw
.pixelstep
[planes
[i
]] +
254 ((s
->h
- s
->y
- frame
->height
) >> vsub
) * frame
->linesize
[planes
[i
]];
256 if (frame
->linesize
[planes
[i
]] < (s
->w
>> hsub
) * s
->draw
.pixelstep
[planes
[i
]])
258 if (start
- buf
->data
< req_start
||
259 (buf
->data
+ buf
->size
) - end
< req_end
)
262 for (j
= 0; j
< FF_ARRAY_ELEMS(planes
) && planes
[j
] >= 0; j
++) {
263 int vsub1
= s
->draw
.vsub
[planes
[j
]];
264 uint8_t *start1
= frame
->data
[planes
[j
]];
265 uint8_t *end1
= start1
+ (frame
->height
>> vsub1
) *
266 frame
->linesize
[planes
[j
]];
270 if (FFSIGN(start
- end1
) != FFSIGN(start
- end1
- req_start
) ||
271 FFSIGN(end
- start1
) != FFSIGN(end
- start1
+ req_end
))
279 static int frame_needs_copy(PadContext
*s
, AVFrame
*frame
)
283 if (!av_frame_is_writable(frame
))
286 for (i
= 0; i
< 4 && frame
->buf
[i
]; i
++)
287 if (buffer_needs_copy(s
, frame
, frame
->buf
[i
]))
292 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*in
)
294 PadContext
*s
= inlink
->dst
->priv
;
296 int needs_copy
= frame_needs_copy(s
, in
);
299 av_log(inlink
->dst
, AV_LOG_DEBUG
, "Direct padding impossible allocating new frame\n");
300 out
= ff_get_video_buffer(inlink
->dst
->outputs
[0],
301 FFMAX(inlink
->w
, s
->w
),
302 FFMAX(inlink
->h
, s
->h
));
305 return AVERROR(ENOMEM
);
308 av_frame_copy_props(out
, in
);
313 for (i
= 0; i
< 4 && out
->data
[i
] && out
->linesize
[i
]; i
++) {
314 int hsub
= s
->draw
.hsub
[i
];
315 int vsub
= s
->draw
.vsub
[i
];
316 out
->data
[i
] -= (s
->x
>> hsub
) * s
->draw
.pixelstep
[i
] +
317 (s
->y
>> vsub
) * out
->linesize
[i
];
323 ff_fill_rectangle(&s
->draw
, &s
->color
,
324 out
->data
, out
->linesize
,
329 if (s
->h
> s
->y
+ s
->in_h
) {
330 ff_fill_rectangle(&s
->draw
, &s
->color
,
331 out
->data
, out
->linesize
,
332 0, s
->y
+ s
->in_h
, s
->w
, s
->h
- s
->y
- s
->in_h
);
336 ff_fill_rectangle(&s
->draw
, &s
->color
, out
->data
, out
->linesize
,
337 0, s
->y
, s
->x
, in
->height
);
340 ff_copy_rectangle2(&s
->draw
,
341 out
->data
, out
->linesize
, in
->data
, in
->linesize
,
342 s
->x
, s
->y
, 0, 0, in
->width
, in
->height
);
346 ff_fill_rectangle(&s
->draw
, &s
->color
, out
->data
, out
->linesize
,
347 s
->x
+ s
->in_w
, s
->y
, s
->w
- s
->x
- s
->in_w
,
355 return ff_filter_frame(inlink
->dst
->outputs
[0], out
);
358 #define OFFSET(x) offsetof(PadContext, x)
359 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
361 static const AVOption pad_options
[] = {
362 { "width", "set the pad area width expression", OFFSET(w_expr
), AV_OPT_TYPE_STRING
, {.str
= "iw"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
363 { "w", "set the pad area width expression", OFFSET(w_expr
), AV_OPT_TYPE_STRING
, {.str
= "iw"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
364 { "height", "set the pad area height expression", OFFSET(h_expr
), AV_OPT_TYPE_STRING
, {.str
= "ih"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
365 { "h", "set the pad area height expression", OFFSET(h_expr
), AV_OPT_TYPE_STRING
, {.str
= "ih"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
366 { "x", "set the x offset expression for the input image position", OFFSET(x_expr
), AV_OPT_TYPE_STRING
, {.str
= "0"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
367 { "y", "set the y offset expression for the input image position", OFFSET(y_expr
), AV_OPT_TYPE_STRING
, {.str
= "0"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
368 { "color", "set the color of the padded area border", OFFSET(rgba_color
), AV_OPT_TYPE_COLOR
, {.str
= "black"}, .flags
= FLAGS
},
372 AVFILTER_DEFINE_CLASS(pad
);
374 static const AVFilterPad avfilter_vf_pad_inputs
[] = {
377 .type
= AVMEDIA_TYPE_VIDEO
,
378 .config_props
= config_input
,
379 .get_video_buffer
= get_video_buffer
,
380 .filter_frame
= filter_frame
,
385 static const AVFilterPad avfilter_vf_pad_outputs
[] = {
388 .type
= AVMEDIA_TYPE_VIDEO
,
389 .config_props
= config_output
,
394 AVFilter ff_vf_pad
= {
396 .description
= NULL_IF_CONFIG_SMALL("Pad the input video."),
397 .priv_size
= sizeof(PadContext
),
398 .priv_class
= &pad_class
,
399 .query_formats
= query_formats
,
400 .inputs
= avfilter_vf_pad_inputs
,
401 .outputs
= avfilter_vf_pad_outputs
,