Browse code

lavfi: add rotate filter

Based on the libavfilter SOC filter by Vitor Sessak, with the following additions:
* integer arithmetic
* bilinear interpolation
* RGB path
* configurable parametric angle, output width and height

Address trac issue #1500.

See thread:
Subject: [FFmpeg-devel] [WIP] rotate filter(s)
Date: 2010-10-03 17:35:49 GMT

Stefano Sabatini authored on 2013/06/11 17:31:59
Showing 6 changed files
... ...
@@ -66,6 +66,7 @@ version <next>:
66 66
 - sab filter ported from libmpcodecs
67 67
 - ffprobe -show_chapters option
68 68
 - WavPack encoding through libwavpack
69
+- rotate filter
69 70
 
70 71
 
71 72
 version 1.2:
... ...
@@ -5771,6 +5771,118 @@ much, but it will increase the amount of blurring needed to cover over
5771 5771
 the image and will destroy more information than necessary, and extra
5772 5772
 pixels will slow things down on a large logo.
5773 5773
 
5774
+@section rotate
5775
+
5776
+Rotate video by an arbitrary angle expressed in radians.
5777
+
5778
+The filter accepts the following options:
5779
+
5780
+A description of the optional parameters follows.
5781
+@table @option
5782
+@item angle, a
5783
+Set an expression for the angle by which to rotate the input video
5784
+clockwise, expressed as a number of radians. A negative value will
5785
+result in a counter-clockwise rotation. By default it is set to "0".
5786
+
5787
+This expression is evaluated for each frame.
5788
+
5789
+@item out_w, ow
5790
+Set the output width expression, default value is "iw".
5791
+This expression is evaluated just once during configuration.
5792
+
5793
+@item out_h, oh
5794
+Set the output height expression, default value is "ih".
5795
+This expression is evaluated just once during configuration.
5796
+
5797
+@item bilinear
5798
+Enable bilinear interpolation if set to 1, a value of 0 disables
5799
+it. Default value is 1.
5800
+
5801
+@item fillcolor, c
5802
+Set the color used to fill the output area not covered by the rotated
5803
+image. If the special value "none" is selected then no background is
5804
+printed (useful for example if the background is never shown). Default
5805
+value is "black".
5806
+@end table
5807
+
5808
+The expressions for the angle and the output size can contain the
5809
+following constants and functions:
5810
+
5811
+@table @option
5812
+@item n
5813
+sequential number of the input frame, starting from 0. It is always NAN
5814
+before the first frame is filtered.
5815
+
5816
+@item t
5817
+time in seconds of the input frame, it is set to 0 when the filter is
5818
+configured. It is always NAN before the first frame is filtered.
5819
+
5820
+@item hsub
5821
+@item vsub
5822
+horizontal and vertical chroma subsample values. For example for the
5823
+pixel format "yuv422p" @var{hsub} is 2 and @var{vsub} is 1.
5824
+
5825
+@item in_w, iw
5826
+@item in_h, ih
5827
+the input video width and heigth
5828
+
5829
+@item out_w, ow
5830
+@item out_h, oh
5831
+the output width and heigth, that is the size of the padded area as
5832
+specified by the @var{width} and @var{height} expressions
5833
+
5834
+@item rotw(a)
5835
+@item roth(a)
5836
+the minimal width/height required for completely containing the input
5837
+video rotated by @var{a} radians.
5838
+
5839
+These are only available when computing the @option{out_w} and
5840
+@option{out_h} expressions.
5841
+@end table
5842
+
5843
+@subsection Examples
5844
+
5845
+@itemize
5846
+@item
5847
+Rotate the input by PI/6 radians clockwise:
5848
+@example
5849
+rotate=PI/6
5850
+@end example
5851
+
5852
+@item
5853
+Rotate the input by PI/6 radians counter-clockwise:
5854
+@example
5855
+rotate=-PI/6
5856
+@end example
5857
+
5858
+@item
5859
+Apply a constant rotation with period T, starting from an angle of PI/3:
5860
+@example
5861
+rotate=PI/3+2*PI*t/T
5862
+@end example
5863
+
5864
+@item
5865
+Make the input video rotation oscillating with a period of T
5866
+seconds and an amplitude of A radians:
5867
+@example
5868
+rotate=A*sin(2*PI/T*t)
5869
+@end example
5870
+
5871
+@item
5872
+Rotate the video, output size is choosen so that the whole rotating
5873
+input video is always completely contained in the output:
5874
+@example
5875
+rotate='2*PI*t:ow=hypot(iw,ih):oh=ow'
5876
+@end example
5877
+
5878
+@item
5879
+Rotate the video, reduce the output size so that no background is ever
5880
+shown:
5881
+@example
5882
+rotate=2*PI*t:ow='min(iw,ih)/sqrt(2)':oh=ow:c=none
5883
+@end example
5884
+@end itemize
5885
+
5774 5886
 @section sab
5775 5887
 
5776 5888
 Apply Shape Adaptive Blur.
... ...
@@ -168,6 +168,7 @@ OBJS-$(CONFIG_PERMS_FILTER)                  += f_perms.o
168 168
 OBJS-$(CONFIG_PIXDESCTEST_FILTER)            += vf_pixdesctest.o
169 169
 OBJS-$(CONFIG_PP_FILTER)                     += vf_pp.o
170 170
 OBJS-$(CONFIG_REMOVELOGO_FILTER)             += bbox.o lswsutils.o lavfutils.o vf_removelogo.o
171
+OBJS-$(CONFIG_ROTATE_FILTER)                 += vf_rotate.o
171 172
 OBJS-$(CONFIG_SEPARATEFIELDS_FILTER)         += vf_separatefields.o
172 173
 OBJS-$(CONFIG_SAB_FILTER)                    += vf_sab.o
173 174
 OBJS-$(CONFIG_SCALE_FILTER)                  += vf_scale.o
... ...
@@ -163,6 +163,7 @@ void avfilter_register_all(void)
163 163
     REGISTER_FILTER(PIXDESCTEST,    pixdesctest,    vf);
164 164
     REGISTER_FILTER(PP,             pp,             vf);
165 165
     REGISTER_FILTER(REMOVELOGO,     removelogo,     vf);
166
+    REGISTER_FILTER(ROTATE,         rotate,         vf);
166 167
     REGISTER_FILTER(SAB,            sab,            vf);
167 168
     REGISTER_FILTER(SCALE,          scale,          vf);
168 169
     REGISTER_FILTER(SELECT,         select,         vf);
... ...
@@ -30,8 +30,8 @@
30 30
 #include "libavutil/avutil.h"
31 31
 
32 32
 #define LIBAVFILTER_VERSION_MAJOR  3
33
-#define LIBAVFILTER_VERSION_MINOR  75
34
-#define LIBAVFILTER_VERSION_MICRO 101
33
+#define LIBAVFILTER_VERSION_MINOR  76
34
+#define LIBAVFILTER_VERSION_MICRO 100
35 35
 
36 36
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
37 37
                                                LIBAVFILTER_VERSION_MINOR, \
38 38
new file mode 100644
... ...
@@ -0,0 +1,441 @@
0
+/*
1
+ * Copyright (c) 2013 Stefano Sabatini
2
+ * Copyright (c) 2008 Vitor Sessak
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
+/**
22
+ * @file
23
+ * rotation filter, partially based on the tests/rotozoom.c program
24
+*/
25
+
26
+#include "libavutil/avstring.h"
27
+#include "libavutil/eval.h"
28
+#include "libavutil/opt.h"
29
+#include "libavutil/intreadwrite.h"
30
+#include "libavutil/parseutils.h"
31
+#include "libavutil/pixdesc.h"
32
+
33
+#include "avfilter.h"
34
+#include "drawutils.h"
35
+#include "internal.h"
36
+#include "video.h"
37
+
38
+static const char *var_names[] = {
39
+    "in_w" , "iw",  ///< width of the input video
40
+    "in_h" , "ih",  ///< height of the input video
41
+    "out_w", "ow",  ///< width of the input video
42
+    "out_h", "oh",  ///< height of the input video
43
+    "hsub", "vsub",
44
+    "n",            ///< number of frame
45
+    "t",            ///< timestamp expressed in seconds
46
+    NULL
47
+};
48
+
49
+enum var_name {
50
+    VAR_IN_W , VAR_IW,
51
+    VAR_IN_H , VAR_IH,
52
+    VAR_OUT_W, VAR_OW,
53
+    VAR_OUT_H, VAR_OH,
54
+    VAR_HSUB, VAR_VSUB,
55
+    VAR_N,
56
+    VAR_T,
57
+    VAR_VARS_NB
58
+};
59
+
60
+typedef struct {
61
+    const AVClass *class;
62
+    double angle;
63
+    char *angle_expr_str;   ///< expression for the angle
64
+    AVExpr *angle_expr;     ///< parsed expression for the angle
65
+    char *outw_expr_str, *outh_expr_str;
66
+    int outh, outw;
67
+    uint8_t fillcolor[4];   ///< color expressed either in YUVA or RGBA colorspace for the padding area
68
+    char *fillcolor_str;
69
+    int fillcolor_enable;
70
+    int hsub, vsub;
71
+    int nb_planes;
72
+    int use_bilinear;
73
+    uint8_t *line[4];
74
+    int linestep[4];
75
+    float sinx, cosx;
76
+    double var_values[VAR_VARS_NB];
77
+} RotContext;
78
+
79
+#define OFFSET(x) offsetof(RotContext, x)
80
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
81
+
82
+static const AVOption rotate_options[] = {
83
+    { "angle",     "set angle (in radians)",       OFFSET(angle_expr_str), AV_OPT_TYPE_STRING, {.str="0"}, CHAR_MIN, CHAR_MAX, .flags=FLAGS },
84
+    { "a",         "set angle (in radians)",       OFFSET(angle_expr_str), AV_OPT_TYPE_STRING, {.str="0"}, CHAR_MIN, CHAR_MAX, .flags=FLAGS },
85
+    { "out_w",     "set output width expression",  OFFSET(outw_expr_str), AV_OPT_TYPE_STRING, {.str="iw"}, CHAR_MIN, CHAR_MAX, .flags=FLAGS },
86
+    { "ow",        "set output width expression",  OFFSET(outw_expr_str), AV_OPT_TYPE_STRING, {.str="iw"}, CHAR_MIN, CHAR_MAX, .flags=FLAGS },
87
+    { "out_h",     "set output height expression", OFFSET(outh_expr_str), AV_OPT_TYPE_STRING, {.str="ih"}, CHAR_MIN, CHAR_MAX, .flags=FLAGS },
88
+    { "oh",        "set output width expression",  OFFSET(outh_expr_str), AV_OPT_TYPE_STRING, {.str="ih"}, CHAR_MIN, CHAR_MAX, .flags=FLAGS },
89
+    { "fillcolor", "set background fill color",    OFFSET(fillcolor_str), AV_OPT_TYPE_STRING, {.str="black"}, CHAR_MIN, CHAR_MAX, .flags=FLAGS },
90
+    { "c",         "set background fill color",    OFFSET(fillcolor_str), AV_OPT_TYPE_STRING, {.str="black"}, CHAR_MIN, CHAR_MAX, .flags=FLAGS },
91
+    { "bilinear",  "use bilinear interpolation",   OFFSET(use_bilinear),  AV_OPT_TYPE_INT, {.i64=1}, 0, 1, .flags=FLAGS },
92
+    { NULL }
93
+};
94
+
95
+AVFILTER_DEFINE_CLASS(rotate);
96
+
97
+static av_cold int init(AVFilterContext *ctx)
98
+{
99
+    RotContext *rot = ctx->priv;
100
+
101
+    if (!strcmp(rot->fillcolor_str, "none"))
102
+        rot->fillcolor_enable = 0;
103
+    else if (av_parse_color(rot->fillcolor, rot->fillcolor_str, -1, ctx) >= 0)
104
+        rot->fillcolor_enable = 1;
105
+    else
106
+        return AVERROR(EINVAL);
107
+    return 0;
108
+}
109
+
110
+static av_cold void uninit(AVFilterContext *ctx)
111
+{
112
+    RotContext *rot = ctx->priv;
113
+    int i;
114
+
115
+    for (i = 0; i < 4; i++)
116
+        av_freep(&rot->line[i]);
117
+
118
+    av_expr_free(rot->angle_expr);
119
+    rot->angle_expr = NULL;
120
+}
121
+
122
+static int query_formats(AVFilterContext *ctx)
123
+{
124
+    static enum PixelFormat pix_fmts[] = {
125
+        AV_PIX_FMT_ARGB,   AV_PIX_FMT_RGBA,
126
+        AV_PIX_FMT_ABGR,   AV_PIX_FMT_BGRA,
127
+        AV_PIX_FMT_RGB24,  AV_PIX_FMT_BGR24,
128
+        AV_PIX_FMT_GRAY8,
129
+        AV_PIX_FMT_YUV444P,  AV_PIX_FMT_YUVJ444P,
130
+        AV_PIX_FMT_YUV420P,  AV_PIX_FMT_YUVJ420P,
131
+        AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA420P,
132
+        AV_PIX_FMT_NONE
133
+    };
134
+
135
+    ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
136
+    return 0;
137
+}
138
+
139
+static double get_rotated_w(void *opaque, double angle)
140
+{
141
+    RotContext *rot = opaque;
142
+    double inw = rot->var_values[VAR_IN_W];
143
+    double inh = rot->var_values[VAR_IN_H];
144
+    float sinx = sin(angle);
145
+    float cosx = cos(angle);
146
+
147
+    return FFMAX(0, inh * sinx) + FFMAX(0, -inw * cosx) +
148
+           FFMAX(0, inw * cosx) + FFMAX(0, -inh * sinx);
149
+}
150
+
151
+static double get_rotated_h(void *opaque, double angle)
152
+{
153
+    RotContext *rot = opaque;
154
+    double inw = rot->var_values[VAR_IN_W];
155
+    double inh = rot->var_values[VAR_IN_H];
156
+    float sinx = sin(angle);
157
+    float cosx = cos(angle);
158
+
159
+    return FFMAX(0, -inh * cosx) + FFMAX(0, -inw * sinx) +
160
+           FFMAX(0,  inh * cosx) + FFMAX(0,  inw * sinx);
161
+}
162
+
163
+static double (* const func1[])(void *, double) = {
164
+    get_rotated_w,
165
+    get_rotated_h,
166
+    NULL
167
+};
168
+
169
+static const char * const func1_names[] = {
170
+    "rotw",
171
+    "roth",
172
+    NULL
173
+};
174
+
175
+static int config_props(AVFilterLink *outlink)
176
+{
177
+    AVFilterContext *ctx = outlink->src;
178
+    RotContext *rot = ctx->priv;
179
+    AVFilterLink *inlink = ctx->inputs[0];
180
+    const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(inlink->format);
181
+    uint8_t rgba_color[4];
182
+    int is_packed_rgba, ret;
183
+    double res;
184
+    char *expr;
185
+
186
+    rot->hsub = pixdesc->log2_chroma_w;
187
+    rot->vsub = pixdesc->log2_chroma_h;
188
+
189
+    rot->var_values[VAR_IN_W] = rot->var_values[VAR_IW] = inlink->w;
190
+    rot->var_values[VAR_IN_H] = rot->var_values[VAR_IH] = inlink->h;
191
+    rot->var_values[VAR_HSUB] = 1<<rot->hsub;
192
+    rot->var_values[VAR_VSUB] = 1<<rot->vsub;
193
+    rot->var_values[VAR_N] = NAN;
194
+    rot->var_values[VAR_T] = NAN;
195
+    rot->var_values[VAR_OUT_W] = rot->var_values[VAR_OW] = NAN;
196
+    rot->var_values[VAR_OUT_H] = rot->var_values[VAR_OH] = NAN;
197
+
198
+    av_expr_free(rot->angle_expr);
199
+    rot->angle_expr = NULL;
200
+    if ((ret = av_expr_parse(&rot->angle_expr, expr = rot->angle_expr_str, var_names,
201
+                             func1_names, func1, NULL, NULL, 0, ctx)) < 0) {
202
+        av_log(ctx, AV_LOG_ERROR,
203
+               "Error occurred parsing angle expression '%s'\n", rot->angle_expr_str);
204
+        return ret;
205
+    }
206
+
207
+#define SET_SIZE_EXPR(name, opt_name) do {                                         \
208
+    ret = av_expr_parse_and_eval(&res, expr = rot->name##_expr_str,                \
209
+                                 var_names, rot->var_values,                       \
210
+                                 func1_names, func1, NULL, NULL, rot, 0, ctx);     \
211
+    if (ret < 0 || isnan(res) || isinf(res) || res <= 0) {                         \
212
+        av_log(ctx, AV_LOG_ERROR,                                                  \
213
+               "Error parsing or evaluating expression for option %s: "            \
214
+               "invalid expression '%s' or non-positive or indefinite value %f\n", \
215
+               opt_name, expr, res);                                               \
216
+        return ret;                                                                \
217
+    }                                                                              \
218
+} while (0)
219
+
220
+    /* evaluate width and height */
221
+    av_expr_parse_and_eval(&res, expr = rot->outw_expr_str, var_names, rot->var_values,
222
+                           func1_names, func1, NULL, NULL, rot, 0, ctx);
223
+    rot->var_values[VAR_OUT_W] = rot->var_values[VAR_OW] = res;
224
+    rot->outw = res + 0.5;
225
+    SET_SIZE_EXPR(outh, "out_w");
226
+    rot->var_values[VAR_OUT_H] = rot->var_values[VAR_OH] = res;
227
+    rot->outh = res + 0.5;
228
+
229
+    /* evaluate the width again, as it may depend on the evaluated output height */
230
+    SET_SIZE_EXPR(outw, "out_h");
231
+    rot->var_values[VAR_OUT_W] = rot->var_values[VAR_OW] = res;
232
+    rot->outw = res + 0.5;
233
+
234
+    /* compute number of planes */
235
+    rot->nb_planes = av_pix_fmt_count_planes(inlink->format);
236
+    outlink->w = rot->outw;
237
+    outlink->h = rot->outh;
238
+
239
+    memcpy(rgba_color, rot->fillcolor, sizeof(rgba_color));
240
+    ff_fill_line_with_color(rot->line, rot->linestep, outlink->w, rot->fillcolor,
241
+                            outlink->format, rgba_color, &is_packed_rgba, NULL);
242
+    av_log(ctx, AV_LOG_INFO,
243
+           "w:%d h:%d -> w:%d h:%d bgcolor:0x%02X%02X%02X%02X[%s]\n",
244
+           inlink->w, inlink->h, outlink->w, outlink->h,
245
+           rot->fillcolor[0], rot->fillcolor[1], rot->fillcolor[2], rot->fillcolor[3],
246
+           is_packed_rgba ? "rgba" : "yuva");
247
+
248
+    return 0;
249
+}
250
+
251
+#define FIXP (1<<16)
252
+#define INT_PI 205887 //(M_PI * FIXP)
253
+
254
+/**
255
+ * Compute the sin of a using integer values.
256
+ * Input and output values are scaled by FIXP.
257
+ */
258
+static int64_t int_sin(int64_t a)
259
+{
260
+    int64_t a2, res = 0;
261
+    int i;
262
+    if (a < 0) a = INT_PI-a; // 0..inf
263
+    a %= 2 * INT_PI;         // 0..2PI
264
+
265
+    if (a >= INT_PI*3/2) a -= 2*INT_PI;  // -PI/2 .. 3PI/2
266
+    if (a >= INT_PI/2  ) a = INT_PI - a; // -PI/2 ..  PI/2
267
+
268
+    /* compute sin using Taylor series approximated to the third term */
269
+    a2 = (a*a)/FIXP;
270
+    for (i = 2; i < 7; i += 2) {
271
+        res += a;
272
+        a = -a*a2 / (FIXP*i*(i+1));
273
+    }
274
+    return res;
275
+}
276
+
277
+/**
278
+ * Interpolate the color in src at position x and y using bilinear
279
+ * interpolation.
280
+ */
281
+static uint8_t *interpolate_bilinear(uint8_t *dst_color,
282
+                                     const uint8_t *src, int src_linesize, int src_linestep,
283
+                                     int x, int y, int max_x, int max_y)
284
+{
285
+    int int_x = av_clip(x>>16, 0, max_x);
286
+    int int_y = av_clip(y>>16, 0, max_y);
287
+    int frac_x = x&0xFFFF;
288
+    int frac_y = y&0xFFFF;
289
+    int i;
290
+    int int_x1 = FFMIN(int_x+1, max_x);
291
+    int int_y1 = FFMIN(int_y+1, max_y);
292
+
293
+    for (i = 0; i < src_linestep; i++) {
294
+        int s00 = src[src_linestep * int_x  + i + src_linesize * int_y ];
295
+        int s01 = src[src_linestep * int_x1 + i + src_linesize * int_y ];
296
+        int s10 = src[src_linestep * int_x  + i + src_linesize * int_y1];
297
+        int s11 = src[src_linestep * int_x1 + i + src_linesize * int_y1];
298
+        int s0 = (((1<<16) - frac_x)*s00 + frac_x*s01);
299
+        int s1 = (((1<<16) - frac_x)*s10 + frac_x*s11);
300
+
301
+        dst_color[i] = ((int64_t)((1<<16) - frac_y)*s0 + (int64_t)frac_y*s1) >> 32;
302
+    }
303
+
304
+    return dst_color;
305
+}
306
+
307
+#define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts)*av_q2d(tb))
308
+
309
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
310
+{
311
+    AVFilterContext *ctx = inlink->dst;
312
+    AVFilterLink *outlink = ctx->outputs[0];
313
+    AVFrame *out;
314
+    RotContext *rot = ctx->priv;
315
+    int angle_int, s, c, plane;
316
+    double res;
317
+
318
+    out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
319
+    if (!out) {
320
+        av_frame_free(&in);
321
+        return AVERROR(ENOMEM);
322
+    }
323
+    av_frame_copy_props(out, in);
324
+
325
+    rot->var_values[VAR_N] = inlink->frame_count;
326
+    rot->var_values[VAR_T] = TS2T(in->pts, inlink->time_base);
327
+    rot->angle = res = av_expr_eval(rot->angle_expr, rot->var_values, rot);
328
+
329
+    av_log(ctx, AV_LOG_DEBUG, "n:%f time:%f angle:%f/PI\n",
330
+           rot->var_values[VAR_N], rot->var_values[VAR_T], rot->angle/M_PI);
331
+
332
+    angle_int = res * FIXP;
333
+    s = int_sin(angle_int);
334
+    c = int_sin(angle_int + INT_PI/2);
335
+
336
+    /* fill background */
337
+    if (rot->fillcolor_enable)
338
+        ff_draw_rectangle(out->data, out->linesize,
339
+                          rot->line, rot->linestep, rot->hsub, rot->vsub,
340
+                          0, 0, outlink->w, outlink->h);
341
+
342
+    for (plane = 0; plane < rot->nb_planes; plane++) {
343
+        int hsub = plane == 1 || plane == 2 ? rot->hsub : 0;
344
+        int vsub = plane == 1 || plane == 2 ? rot->vsub : 0;
345
+        int inw  = FF_CEIL_RSHIFT(inlink->w, hsub);
346
+        int inh  = FF_CEIL_RSHIFT(inlink->h, vsub);
347
+        int outw = FF_CEIL_RSHIFT(outlink->w, hsub);
348
+        int outh = FF_CEIL_RSHIFT(outlink->h, hsub);
349
+
350
+        const int xi = -outw/2 * c;
351
+        const int yi =  outw/2 * s;
352
+        int xprime = -outh/2 * s;
353
+        int yprime = -outh/2 * c;
354
+        int i, j, x, y;
355
+
356
+        for (j = 0; j < outh; j++) {
357
+            x = xprime + xi + FIXP*inw/2;
358
+            y = yprime + yi + FIXP*inh/2;
359
+
360
+            for (i = 0; i < outw; i++) {
361
+                int32_t v;
362
+                int x1, y1;
363
+                uint8_t *pin, *pout;
364
+                x += c;
365
+                y -= s;
366
+                x1 = x>>16;
367
+                y1 = y>>16;
368
+
369
+                /* the out-of-range values avoid border artifacts */
370
+                if (x1 >= -1 && x1 <= inw && y1 >= -1 && y1 <= inh) {
371
+                    uint8_t inp_inv[4]; /* interpolated input value */
372
+                    pout = out->data[plane] + j * out->linesize[plane] + i * rot->linestep[plane];
373
+                    if (rot->use_bilinear) {
374
+                        pin = interpolate_bilinear(inp_inv,
375
+                                                   in->data[plane], in->linesize[plane], rot->linestep[plane],
376
+                                                   x, y, inw-1, inh-1);
377
+                    } else {
378
+                        int x2 = av_clip(x1, 0, inw-1);
379
+                        int y2 = av_clip(y1, 0, inh-1);
380
+                        pin = in->data[plane] + y2 * in->linesize[plane] + x2 * rot->linestep[plane];
381
+                    }
382
+                    switch (rot->linestep[plane]) {
383
+                    case 1:
384
+                        *pout = *pin;
385
+                        break;
386
+                    case 2:
387
+                        *((uint16_t *)pout) = *((uint16_t *)pin);
388
+                        break;
389
+                    case 3:
390
+                        v = AV_RB24(pin);
391
+                        AV_WB24(pout, v);
392
+                        break;
393
+                    case 4:
394
+                        *((uint32_t *)pout) = *((uint32_t *)pin);
395
+                        break;
396
+                    default:
397
+                        memcpy(pout, pin, rot->linestep[plane]);
398
+                        break;
399
+                    }
400
+                }
401
+            }
402
+            xprime += s;
403
+            yprime += c;
404
+        }
405
+    }
406
+
407
+    av_frame_free(&in);
408
+    return ff_filter_frame(outlink, out);
409
+}
410
+
411
+static const AVFilterPad rotate_inputs[] = {
412
+    {
413
+        .name         = "default",
414
+        .type         = AVMEDIA_TYPE_VIDEO,
415
+        .filter_frame = filter_frame,
416
+    },
417
+    { NULL }
418
+};
419
+
420
+static const AVFilterPad rotate_outputs[] = {
421
+    {
422
+        .name         = "default",
423
+        .type         = AVMEDIA_TYPE_VIDEO,
424
+        .config_props = config_props,
425
+    },
426
+    { NULL }
427
+};
428
+
429
+AVFilter avfilter_vf_rotate = {
430
+    .name          = "rotate",
431
+    .description   = NULL_IF_CONFIG_SMALL("Rotate the input image."),
432
+    .priv_size     = sizeof(RotContext),
433
+    .init          = init,
434
+    .uninit        = uninit,
435
+    .query_formats = query_formats,
436
+    .inputs        = rotate_inputs,
437
+    .outputs       = rotate_outputs,
438
+    .priv_class    = &rotate_class,
439
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
440
+};