avfilter/vf_swaprect: add timeline support
[ffmpeg.git] / libavfilter / vf_swaprect.c
1 /*
2  * Copyright (c) 2015 Paul B. Mahol
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20
21 #include "libavutil/avstring.h"
22 #include "libavutil/eval.h"
23 #include "libavutil/imgutils.h"
24 #include "libavutil/opt.h"
25 #include "libavutil/pixdesc.h"
26
27 #include "avfilter.h"
28 #include "formats.h"
29 #include "internal.h"
30 #include "video.h"
31
32 typedef struct SwapRectContext {
33     const AVClass *class;
34     char *w, *h;
35     char *x1, *y1;
36     char *x2, *y2;
37
38     int nb_planes;
39     int pixsteps[4];
40
41     const AVPixFmtDescriptor *desc;
42     uint8_t *temp;
43 } SwapRectContext;
44
45 #define OFFSET(x) offsetof(SwapRectContext, x)
46 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
47 static const AVOption swaprect_options[] = {
48     { "w",  "set rect width",                     OFFSET(w),  AV_OPT_TYPE_STRING, {.str="w/2"}, 0, 0, .flags = FLAGS },
49     { "h",  "set rect height",                    OFFSET(h),  AV_OPT_TYPE_STRING, {.str="h/2"}, 0, 0, .flags = FLAGS },
50     { "x1", "set 1st rect x top left coordinate", OFFSET(x1), AV_OPT_TYPE_STRING, {.str="w/2"}, 0, 0, .flags = FLAGS },
51     { "y1", "set 1st rect y top left coordinate", OFFSET(y1), AV_OPT_TYPE_STRING, {.str="h/2"}, 0, 0, .flags = FLAGS },
52     { "x2", "set 2nd rect x top left coordinate", OFFSET(x2), AV_OPT_TYPE_STRING, {.str="0"},   0, 0, .flags = FLAGS },
53     { "y2", "set 2nd rect y top left coordinate", OFFSET(y2), AV_OPT_TYPE_STRING, {.str="0"},   0, 0, .flags = FLAGS },
54     { NULL },
55 };
56
57 AVFILTER_DEFINE_CLASS(swaprect);
58
59 static int query_formats(AVFilterContext *ctx)
60 {
61     AVFilterFormats *pix_fmts = NULL;
62     int fmt, ret;
63
64     for (fmt = 0; av_pix_fmt_desc_get(fmt); fmt++) {
65         const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
66         if (!(desc->flags & AV_PIX_FMT_FLAG_PAL ||
67               desc->flags & AV_PIX_FMT_FLAG_HWACCEL ||
68               desc->flags & AV_PIX_FMT_FLAG_BITSTREAM) &&
69             (ret = ff_add_format(&pix_fmts, fmt)) < 0)
70             return ret;
71     }
72
73     return ff_set_common_formats(ctx, pix_fmts);
74 }
75
76 static const char *const var_names[] = {   "w",   "h",   "a",   "n",   "t",   "pos",   "sar",   "dar",        NULL };
77 enum                                   { VAR_W, VAR_H, VAR_A, VAR_N, VAR_T, VAR_POS, VAR_SAR, VAR_DAR, VAR_VARS_NB };
78
79 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
80 {
81     AVFilterContext *ctx = inlink->dst;
82     AVFilterLink *outlink = ctx->outputs[0];
83     SwapRectContext *s = ctx->priv;
84     double var_values[VAR_VARS_NB];
85     int x1[4], y1[4];
86     int x2[4], y2[4];
87     int aw[4], ah[4];
88     int lw[4], lh[4];
89     int pw[4], ph[4];
90     double dw,  dh;
91     double dx1, dy1;
92     double dx2, dy2;
93     int y, p, w, h, ret;
94
95     var_values[VAR_W]   = inlink->w;
96     var_values[VAR_H]   = inlink->h;
97     var_values[VAR_A]   = (float) inlink->w / inlink->h;
98     var_values[VAR_SAR] = inlink->sample_aspect_ratio.num ? av_q2d(inlink->sample_aspect_ratio) : 1;
99     var_values[VAR_DAR] = var_values[VAR_A] * var_values[VAR_SAR];
100     var_values[VAR_N]   = inlink->frame_count;
101     var_values[VAR_T]   = in->pts == AV_NOPTS_VALUE ? NAN : in->pts * av_q2d(inlink->time_base);
102     var_values[VAR_POS] = av_frame_get_pkt_pos(in) == -1 ? NAN : av_frame_get_pkt_pos(in);
103
104     ret = av_expr_parse_and_eval(&dw, s->w,
105                                  var_names, &var_values[0],
106                                  NULL, NULL, NULL, NULL,
107                                  0, 0, ctx);
108     if (ret < 0)
109         return ret;
110
111     ret = av_expr_parse_and_eval(&dh, s->h,
112                                  var_names, &var_values[0],
113                                  NULL, NULL, NULL, NULL,
114                                  0, 0, ctx);
115     if (ret < 0)
116         return ret;
117
118     ret = av_expr_parse_and_eval(&dx1, s->x1,
119                                  var_names, &var_values[0],
120                                  NULL, NULL, NULL, NULL,
121                                  0, 0, ctx);
122     if (ret < 0)
123         return ret;
124
125     ret = av_expr_parse_and_eval(&dy1, s->y1,
126                                  var_names, &var_values[0],
127                                  NULL, NULL, NULL, NULL,
128                                  0, 0, ctx);
129     if (ret < 0)
130         return ret;
131
132     ret = av_expr_parse_and_eval(&dx2, s->x2,
133                                  var_names, &var_values[0],
134                                  NULL, NULL, NULL, NULL,
135                                  0, 0, ctx);
136     if (ret < 0)
137         return ret;
138
139     ret = av_expr_parse_and_eval(&dy2, s->y2,
140                                  var_names, &var_values[0],
141                                  NULL, NULL, NULL, NULL,
142                                  0, 0, ctx);
143     if (ret < 0)
144         return ret;
145
146     w = dw; h = dh; x1[0] = dx1; y1[0] = dy1; x2[0] = dx2; y2[0] = dy2;
147
148     x1[0] = av_clip(x1[0], 0, inlink->w - 1);
149     y1[0] = av_clip(y1[0], 0, inlink->w - 1);
150
151     x2[0] = av_clip(x2[0], 0, inlink->w - 1);
152     y2[0] = av_clip(y2[0], 0, inlink->w - 1);
153
154     ah[1] = ah[2] = FF_CEIL_RSHIFT(h, s->desc->log2_chroma_h);
155     ah[0] = ah[3] = h;
156     aw[1] = aw[2] = FF_CEIL_RSHIFT(w, s->desc->log2_chroma_w);
157     aw[0] = aw[3] = w;
158
159     w = FFMIN3(w, inlink->w - x1[0], inlink->w - x2[0]);
160     h = FFMIN3(h, inlink->h - y1[0], inlink->h - y2[0]);
161
162     ph[1] = ph[2] = FF_CEIL_RSHIFT(h, s->desc->log2_chroma_h);
163     ph[0] = ph[3] = h;
164     pw[1] = pw[2] = FF_CEIL_RSHIFT(w, s->desc->log2_chroma_w);
165     pw[0] = pw[3] = w;
166
167     lh[1] = lh[2] = FF_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
168     lh[0] = lh[3] = inlink->h;
169     lw[1] = lw[2] = FF_CEIL_RSHIFT(inlink->w, s->desc->log2_chroma_w);
170     lw[0] = lw[3] = inlink->w;
171
172     x1[1] = x1[2] = FF_CEIL_RSHIFT(x1[0], s->desc->log2_chroma_w);
173     x1[0] = x1[3] = x1[0];
174     y1[1] = y1[2] = FF_CEIL_RSHIFT(y1[0], s->desc->log2_chroma_h);
175     y1[0] = y1[3] = y1[0];
176
177     x2[1] = x2[2] = FF_CEIL_RSHIFT(x2[0], s->desc->log2_chroma_w);
178     x2[0] = x2[3] = x2[0];
179     y2[1] = y2[2] = FF_CEIL_RSHIFT(y2[0], s->desc->log2_chroma_h);
180     y2[0] = y2[3] = y2[0];
181
182     for (p = 0; p < s->nb_planes; p++) {
183         if (ph[p] == ah[p] && pw[p] == aw[p]) {
184             uint8_t *src = in->data[p] + y1[p] * in->linesize[p] + x1[p] * s->pixsteps[p];
185             uint8_t *dst = in->data[p] + y2[p] * in->linesize[p] + x2[p] * s->pixsteps[p];
186
187             for (y = 0; y < ph[p]; y++) {
188                 memcpy(s->temp, src, pw[p] * s->pixsteps[p]);
189                 memmove(src, dst, pw[p] * s->pixsteps[p]);
190                 memcpy(dst, s->temp, pw[p] * s->pixsteps[p]);
191                 src += in->linesize[p];
192                 dst += in->linesize[p];
193             }
194         }
195     }
196
197     return ff_filter_frame(outlink, in);
198 }
199
200 static int config_input(AVFilterLink *inlink)
201 {
202     AVFilterContext *ctx = inlink->dst;
203     SwapRectContext *s = ctx->priv;
204
205     if (!s->w  || !s->h  ||
206         !s->x1 || !s->y1 ||
207         !s->x2 || !s->y2)
208         return AVERROR(EINVAL);
209
210     s->desc = av_pix_fmt_desc_get(inlink->format);
211     av_image_fill_max_pixsteps(s->pixsteps, NULL, s->desc);
212     s->nb_planes = av_pix_fmt_count_planes(inlink->format);
213
214     s->temp = av_malloc_array(inlink->w, s->pixsteps[0]);
215     if (!s->temp)
216         return AVERROR(ENOMEM);
217
218     return 0;
219 }
220
221 static av_cold void uninit(AVFilterContext *ctx)
222 {
223     SwapRectContext *s = ctx->priv;
224     av_freep(&s->temp);
225 }
226
227 static const AVFilterPad inputs[] = {
228     {
229         .name           = "default",
230         .type           = AVMEDIA_TYPE_VIDEO,
231         .filter_frame   = filter_frame,
232         .config_props   = config_input,
233         .needs_writable = 1,
234     },
235     { NULL }
236 };
237
238 static const AVFilterPad outputs[] = {
239     {
240         .name = "default",
241         .type = AVMEDIA_TYPE_VIDEO,
242     },
243     { NULL }
244 };
245
246 AVFilter ff_vf_swaprect = {
247     .name          = "swaprect",
248     .description   = NULL_IF_CONFIG_SMALL("Swap 2 rectangular objects in video."),
249     .priv_size     = sizeof(SwapRectContext),
250     .priv_class    = &swaprect_class,
251     .query_formats = query_formats,
252     .uninit        = uninit,
253     .inputs        = inputs,
254     .outputs       = outputs,
255     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
256 };