Commit | Line | Data |
---|---|---|
3c940173 MN |
1 | /* |
2 | * copyright (c) 2008 vmrsss | |
3 | * copyright (c) 2009 Stefano Sabatini | |
4 | * | |
5 | * This file is part of FFmpeg. | |
6 | * | |
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. | |
11 | * | |
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. | |
16 | * | |
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 | |
20 | */ | |
21 | ||
22 | /** | |
23 | * @file libavfilter/vf_pad.c | |
24 | * video padding filter | |
25 | */ | |
26 | ||
27 | #include "avfilter.h" | |
28 | #include "parseutils.h" | |
29 | #include "libavutil/pixdesc.h" | |
30 | #include "libavcodec/colorspace.h" | |
31 | ||
32 | typedef struct { | |
33 | int w, h; ///< output dimensions, a value of 0 will result in the input size | |
34 | int x, y; ///< offsets of the input area with respect to the padded area | |
35 | 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 | |
36 | ||
37 | uint8_t color[4]; ///< color expressed either in YUVA or RGBA colorspace for the padding area | |
38 | uint8_t *line[4]; | |
39 | int line_step[4]; | |
40 | int hsub, vsub; ///< chroma subsampling values | |
41 | } PadContext; | |
42 | ||
43 | static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque) | |
44 | { | |
45 | PadContext *pad = ctx->priv; | |
46 | char color_string[128] = "black"; | |
47 | ||
48 | if (args) | |
49 | sscanf(args, "%d:%d:%d:%d:%s", &pad->w, &pad->h, &pad->x, &pad->y, color_string); | |
50 | ||
51 | if (av_parse_color(pad->color, color_string, ctx) < 0) | |
52 | return -1; | |
53 | ||
54 | /* sanity check params */ | |
55 | if (pad->w < 0 || pad->h < 0) { | |
56 | av_log(ctx, AV_LOG_ERROR, "Negative size values are not acceptable.\n"); | |
57 | return -1; | |
58 | } | |
59 | ||
60 | return 0; | |
61 | } | |
62 | ||
63 | static av_cold void uninit(AVFilterContext *ctx) | |
64 | { | |
65 | PadContext *pad = ctx->priv; | |
66 | int i; | |
67 | ||
68 | for (i = 0; i < 4; i++) { | |
69 | av_freep(&pad->line[i]); | |
70 | pad->line_step[i] = 0; | |
71 | } | |
72 | } | |
73 | ||
74 | static int query_formats(AVFilterContext *ctx) | |
75 | { | |
76 | static const enum PixelFormat pix_fmts[] = { | |
77 | PIX_FMT_ARGB, PIX_FMT_RGBA, | |
78 | PIX_FMT_ABGR, PIX_FMT_BGRA, | |
79 | PIX_FMT_RGB24, PIX_FMT_BGR24, | |
80 | ||
81 | PIX_FMT_YUV444P, PIX_FMT_YUV422P, | |
82 | PIX_FMT_YUV420P, PIX_FMT_YUV411P, | |
83 | PIX_FMT_YUV410P, PIX_FMT_YUV440P, | |
84 | PIX_FMT_YUVJ444P, PIX_FMT_YUVJ422P, | |
85 | PIX_FMT_YUVJ420P, PIX_FMT_YUVJ440P, | |
86 | PIX_FMT_YUVA420P, | |
87 | ||
88 | PIX_FMT_NONE | |
89 | }; | |
90 | ||
91 | avfilter_set_common_formats(ctx, avfilter_make_format_list(pix_fmts)); | |
92 | return 0; | |
93 | } | |
94 | ||
95 | enum { RED = 0, GREEN, BLUE, ALPHA }; | |
96 | ||
97 | static int config_input(AVFilterLink *inlink) | |
98 | { | |
99 | AVFilterContext *ctx = inlink->dst; | |
100 | PadContext *pad = ctx->priv; | |
101 | const AVPixFmtDescriptor *pix_desc = &av_pix_fmt_descriptors[inlink->format]; | |
102 | uint8_t rgba_color[4]; | |
103 | uint8_t rgba_map[4]; | |
104 | int i, is_packed_rgb = 1; | |
105 | ||
106 | switch (inlink->format) { | |
107 | case PIX_FMT_ARGB: | |
108 | rgba_map[ALPHA] = 0; rgba_map[RED] = 1; rgba_map[GREEN] = 2; rgba_map[BLUE] = 3; | |
109 | break; | |
110 | case PIX_FMT_ABGR: | |
111 | rgba_map[ALPHA] = 0; rgba_map[BLUE] = 1; rgba_map[GREEN] = 2; rgba_map[RED] = 3; | |
112 | break; | |
113 | case PIX_FMT_RGBA: | |
114 | case PIX_FMT_RGB24: | |
115 | rgba_map[RED] = 0; rgba_map[GREEN] = 1; rgba_map[BLUE] = 2; rgba_map[ALPHA] = 3; | |
116 | break; | |
117 | case PIX_FMT_BGRA: | |
118 | case PIX_FMT_BGR24: | |
119 | rgba_map[BLUE] = 0; rgba_map[GREEN] = 1; rgba_map[RED] = 2; rgba_map[ALPHA] = 3; | |
120 | break; | |
121 | default: | |
122 | is_packed_rgb = 0; | |
123 | } | |
124 | ||
125 | pad->hsub = pix_desc->log2_chroma_w; | |
126 | pad->vsub = pix_desc->log2_chroma_h; | |
127 | ||
128 | if (!pad->w) | |
129 | pad->w = inlink->w; | |
130 | if (!pad->h) | |
131 | pad->h = inlink->h; | |
132 | ||
133 | pad->w &= ~((1 << pad->hsub) - 1); | |
134 | pad->h &= ~((1 << pad->vsub) - 1); | |
135 | pad->x &= ~((1 << pad->hsub) - 1); | |
136 | pad->y &= ~((1 << pad->vsub) - 1); | |
137 | ||
138 | pad->in_w = inlink->w & ~((1 << pad->hsub) - 1); | |
139 | pad->in_h = inlink->h & ~((1 << pad->vsub) - 1); | |
140 | ||
141 | memcpy(rgba_color, pad->color, sizeof(rgba_color)); | |
142 | if (is_packed_rgb) { | |
143 | pad->line_step[0] = (av_get_bits_per_pixel(&av_pix_fmt_descriptors[inlink->format]))>>3; | |
144 | for (i = 0; i < 4; i++) | |
145 | pad->color[rgba_map[i]] = rgba_color[i]; | |
146 | ||
147 | pad->line[0] = av_malloc(pad->w * pad->line_step[0]); | |
148 | for (i = 0; i < pad->w; i++) | |
149 | memcpy(pad->line[0] + i * pad->line_step[0], pad->color, pad->line_step[0]); | |
150 | } else { | |
151 | int plane; | |
152 | ||
153 | pad->color[0] = RGB_TO_Y_CCIR(rgba_color[0], rgba_color[1], rgba_color[2]); | |
154 | pad->color[1] = RGB_TO_U_CCIR(rgba_color[0], rgba_color[1], rgba_color[2], 0); | |
155 | pad->color[2] = RGB_TO_V_CCIR(rgba_color[0], rgba_color[1], rgba_color[2], 0); | |
156 | pad->color[3] = rgba_color[3]; | |
157 | ||
158 | for (plane = 0; plane < 4; plane++) { | |
159 | int line_size; | |
160 | int hsub = (plane == 1 || plane == 2) ? pad->hsub : 0; | |
161 | ||
162 | pad->line_step[plane] = 1; | |
163 | line_size = (pad->w >> hsub) * pad->line_step[plane]; | |
164 | pad->line[plane] = av_malloc(line_size); | |
165 | memset(pad->line[plane], pad->color[plane], line_size); | |
166 | } | |
167 | } | |
168 | ||
169 | av_log(ctx, AV_LOG_INFO, "w:%d h:%d x:%d y:%d color:0x%02X%02X%02X%02X[%s]\n", | |
170 | pad->w, pad->h, pad->x, pad->y, | |
171 | pad->color[0], pad->color[1], pad->color[2], pad->color[3], | |
172 | is_packed_rgb ? "rgba" : "yuva"); | |
173 | ||
174 | if (pad->x < 0 || pad->y < 0 || | |
175 | pad->w <= 0 || pad->h <= 0 || | |
176 | (unsigned)pad->x + (unsigned)inlink->w > pad->w || | |
177 | (unsigned)pad->y + (unsigned)inlink->h > pad->h) { | |
178 | av_log(ctx, AV_LOG_ERROR, | |
179 | "Input area %d:%d:%d:%d not within the padded area 0:0:%d:%d or zero-sized\n", | |
180 | pad->x, pad->y, pad->x + inlink->w, pad->y + inlink->h, pad->w, pad->h); | |
181 | return -1; | |
182 | } | |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
187 | static int config_output(AVFilterLink *outlink) | |
188 | { | |
189 | PadContext *pad = outlink->src->priv; | |
190 | ||
191 | outlink->w = pad->w; | |
192 | outlink->h = pad->h; | |
193 | return 0; | |
194 | } | |
195 | ||
196 | static AVFilterPicRef *get_video_buffer(AVFilterLink *inlink, int perms, int w, int h) | |
197 | { | |
198 | PadContext *pad = inlink->dst->priv; | |
199 | ||
200 | AVFilterPicRef *picref = avfilter_get_video_buffer(inlink->dst->outputs[0], perms, | |
201 | w + (pad->w - pad->in_w), | |
202 | h + (pad->h - pad->in_h)); | |
f66b3905 | 203 | int plane; |
3c940173 | 204 | |
f66b3905 | 205 | for (plane = 0; plane < 4 && picref->data[plane]; plane++) { |
3c940173 MN |
206 | int hsub = (plane == 1 || plane == 2) ? pad->hsub : 0; |
207 | int vsub = (plane == 1 || plane == 2) ? pad->vsub : 0; | |
208 | ||
209 | picref->data[plane] += (pad->x >> hsub) * pad->line_step[plane] + | |
210 | (pad->y >> vsub) * picref->linesize[plane]; | |
211 | } | |
212 | ||
213 | return picref; | |
214 | } | |
215 | ||
216 | static void start_frame(AVFilterLink *inlink, AVFilterPicRef *inpicref) | |
217 | { | |
218 | PadContext *pad = inlink->dst->priv; | |
219 | AVFilterPicRef *outpicref = avfilter_ref_pic(inpicref, ~0); | |
f66b3905 MR |
220 | int plane; |
221 | ||
3c940173 MN |
222 | inlink->dst->outputs[0]->outpic = outpicref; |
223 | ||
f66b3905 | 224 | for (plane = 0; plane < 4 && outpicref->data[plane]; plane++) { |
3c940173 MN |
225 | int hsub = (plane == 1 || plane == 2) ? pad->hsub : 0; |
226 | int vsub = (plane == 1 || plane == 2) ? pad->vsub : 0; | |
227 | ||
228 | outpicref->data[plane] -= (pad->x >> hsub) * pad->line_step[plane] + | |
229 | (pad->y >> vsub) * outpicref->linesize[plane]; | |
230 | } | |
231 | ||
232 | avfilter_start_frame(inlink->dst->outputs[0], outpicref); | |
233 | } | |
234 | ||
235 | static void end_frame(AVFilterLink *link) | |
236 | { | |
237 | avfilter_end_frame(link->dst->outputs[0]); | |
238 | avfilter_unref_pic(link->cur_pic); | |
239 | } | |
240 | ||
241 | static void draw_rectangle(AVFilterPicRef *outpic, uint8_t *line[4], int line_step[4], | |
242 | int hsub, int vsub, int x, int y, int w, int h) | |
243 | { | |
244 | int i, plane; | |
245 | uint8_t *p; | |
246 | ||
247 | for (plane = 0; plane < 4 && outpic->data[plane]; plane++) { | |
248 | int hsub1 = plane == 1 || plane == 2 ? hsub : 0; | |
249 | int vsub1 = plane == 1 || plane == 2 ? vsub : 0; | |
250 | ||
251 | p = outpic->data[plane] + (y >> vsub1) * outpic->linesize[plane]; | |
252 | for (i = 0; i < (h >> vsub1); i++) { | |
253 | memcpy(p + (x >> hsub1) * line_step[plane], line[plane], (w >> hsub1) * line_step[plane]); | |
254 | p += outpic->linesize[plane]; | |
255 | } | |
256 | } | |
257 | } | |
258 | ||
259 | static void draw_send_bar_slice(AVFilterLink *link, int y, int h, int slice_dir, int before_slice) | |
260 | { | |
261 | PadContext *pad = link->dst->priv; | |
262 | int bar_y, bar_h = 0; | |
263 | ||
264 | if (slice_dir * before_slice == 1 && y == pad->y) { | |
265 | /* top bar */ | |
266 | bar_y = 0; | |
267 | bar_h = pad->y; | |
268 | } else if (slice_dir * before_slice == -1 && (y + h) == (pad->y + pad->in_h)) { | |
269 | /* bottom bar */ | |
270 | bar_y = pad->y + pad->in_h; | |
271 | bar_h = pad->h - pad->in_h - pad->y; | |
272 | } | |
273 | ||
274 | if (bar_h) { | |
275 | draw_rectangle(link->dst->outputs[0]->outpic, | |
276 | pad->line, pad->line_step, pad->hsub, pad->vsub, | |
277 | 0, bar_y, pad->w, bar_h); | |
278 | avfilter_draw_slice(link->dst->outputs[0], bar_y, bar_h, slice_dir); | |
279 | } | |
280 | } | |
281 | ||
282 | static void draw_slice(AVFilterLink *link, int y, int h, int slice_dir) | |
283 | { | |
284 | PadContext *pad = link->dst->priv; | |
285 | AVFilterPicRef *outpic = link->dst->outputs[0]->outpic; | |
286 | ||
287 | y += pad->y; | |
288 | ||
289 | y &= ~((1 << pad->vsub) - 1); | |
290 | h &= ~((1 << pad->vsub) - 1); | |
291 | ||
292 | if (!h) | |
293 | return; | |
294 | draw_send_bar_slice(link, y, h, slice_dir, 1); | |
295 | ||
296 | /* left border */ | |
297 | draw_rectangle(outpic, pad->line, pad->line_step, pad->hsub, pad->vsub, | |
298 | 0, y, pad->x, h); | |
299 | /* right border */ | |
300 | draw_rectangle(outpic, pad->line, pad->line_step, pad->hsub, pad->vsub, | |
301 | pad->x + pad->in_w, y, pad->w - pad->x - pad->in_w, h); | |
302 | avfilter_draw_slice(link->dst->outputs[0], y, h, slice_dir); | |
303 | ||
304 | draw_send_bar_slice(link, y, h, slice_dir, -1); | |
305 | } | |
306 | ||
307 | AVFilter avfilter_vf_pad = { | |
308 | .name = "pad", | |
309 | .description = "Add pads to the input image.", | |
310 | ||
311 | .priv_size = sizeof(PadContext), | |
312 | .init = init, | |
313 | .uninit = uninit, | |
314 | .query_formats = query_formats, | |
315 | ||
316 | .inputs = (AVFilterPad[]) {{ .name = "default", | |
317 | .type = AVMEDIA_TYPE_VIDEO, | |
318 | .config_props = config_input, | |
319 | .get_video_buffer = get_video_buffer, | |
320 | .start_frame = start_frame, | |
321 | .draw_slice = draw_slice, | |
322 | .end_frame = end_frame, }, | |
323 | { .name = NULL}}, | |
324 | ||
325 | .outputs = (AVFilterPad[]) {{ .name = "default", | |
326 | .type = AVMEDIA_TYPE_VIDEO, | |
327 | .config_props = config_output, }, | |
328 | { .name = NULL}}, | |
329 | }; |