]> git.sesse.net Git - ffmpeg/blob - libavfilter/vf_v360.c
avfilter/vf_v360: handle gracefully invalid values for rorder option
[ffmpeg] / libavfilter / vf_v360.c
1 /*
2  * Copyright (c) 2019 Eugene Lyapustin
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  * 360 video conversion filter.
24  * Principle of operation:
25  *
26  * (for each pixel in output frame)
27  * 1) Calculate OpenGL-like coordinates (x, y, z) for pixel position (i, j)
28  * 2) Apply 360 operations (rotation, mirror) to (x, y, z)
29  * 3) Calculate pixel position (u, v) in input frame
30  * 4) Calculate interpolation window and weight for each pixel
31  *
32  * (for each frame)
33  * 5) Remap input frame to output frame using precalculated data
34  */
35
36 #include <math.h>
37
38 #include "libavutil/avassert.h"
39 #include "libavutil/imgutils.h"
40 #include "libavutil/pixdesc.h"
41 #include "libavutil/opt.h"
42 #include "avfilter.h"
43 #include "formats.h"
44 #include "internal.h"
45 #include "video.h"
46 #include "v360.h"
47
48 typedef struct ThreadData {
49     AVFrame *in;
50     AVFrame *out;
51 } ThreadData;
52
53 #define OFFSET(x) offsetof(V360Context, x)
54 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
55
56 static const AVOption v360_options[] = {
57     {     "input", "set input projection",              OFFSET(in), AV_OPT_TYPE_INT,    {.i64=EQUIRECTANGULAR}, 0,    NB_PROJECTIONS-1, FLAGS, "in" },
58     {         "e", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "in" },
59     {  "equirect", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "in" },
60     {      "c3x2", "cubemap 3x2",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_3_2},     0,                   0, FLAGS, "in" },
61     {      "c6x1", "cubemap 6x1",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_6_1},     0,                   0, FLAGS, "in" },
62     {       "eac", "equi-angular cubemap",                       0, AV_OPT_TYPE_CONST,  {.i64=EQUIANGULAR},     0,                   0, FLAGS, "in" },
63     {  "dfisheye", "dual fisheye",                               0, AV_OPT_TYPE_CONST,  {.i64=DUAL_FISHEYE},    0,                   0, FLAGS, "in" },
64     {      "flat", "regular video",                              0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "in" },
65     {"rectilinear", "regular video",                             0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "in" },
66     {  "gnomonic", "regular video",                              0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "in" },
67     {    "barrel", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "in" },
68     {        "fb", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "in" },
69     {      "c1x6", "cubemap 1x6",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_1_6},     0,                   0, FLAGS, "in" },
70     {        "sg", "stereographic",                              0, AV_OPT_TYPE_CONST,  {.i64=STEREOGRAPHIC},   0,                   0, FLAGS, "in" },
71     {  "mercator", "mercator",                                   0, AV_OPT_TYPE_CONST,  {.i64=MERCATOR},        0,                   0, FLAGS, "in" },
72     {      "ball", "ball",                                       0, AV_OPT_TYPE_CONST,  {.i64=BALL},            0,                   0, FLAGS, "in" },
73     {    "hammer", "hammer",                                     0, AV_OPT_TYPE_CONST,  {.i64=HAMMER},          0,                   0, FLAGS, "in" },
74     {"sinusoidal", "sinusoidal",                                 0, AV_OPT_TYPE_CONST,  {.i64=SINUSOIDAL},      0,                   0, FLAGS, "in" },
75     {   "fisheye", "fisheye",                                    0, AV_OPT_TYPE_CONST,  {.i64=FISHEYE},         0,                   0, FLAGS, "in" },
76     {"cylindrical", "cylindrical",                               0, AV_OPT_TYPE_CONST,  {.i64=CYLINDRICAL},     0,                   0, FLAGS, "in" },
77     {"tetrahedron", "tetrahedron",                               0, AV_OPT_TYPE_CONST,  {.i64=TETRAHEDRON},     0,                   0, FLAGS, "in" },
78     {    "output", "set output projection",            OFFSET(out), AV_OPT_TYPE_INT,    {.i64=CUBEMAP_3_2},     0,    NB_PROJECTIONS-1, FLAGS, "out" },
79     {         "e", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "out" },
80     {  "equirect", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "out" },
81     {      "c3x2", "cubemap 3x2",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_3_2},     0,                   0, FLAGS, "out" },
82     {      "c6x1", "cubemap 6x1",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_6_1},     0,                   0, FLAGS, "out" },
83     {       "eac", "equi-angular cubemap",                       0, AV_OPT_TYPE_CONST,  {.i64=EQUIANGULAR},     0,                   0, FLAGS, "out" },
84     {  "dfisheye", "dual fisheye",                               0, AV_OPT_TYPE_CONST,  {.i64=DUAL_FISHEYE},    0,                   0, FLAGS, "out" },
85     {      "flat", "regular video",                              0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "out" },
86     {"rectilinear", "regular video",                             0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "out" },
87     {  "gnomonic", "regular video",                              0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "out" },
88     {    "barrel", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "out" },
89     {        "fb", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "out" },
90     {      "c1x6", "cubemap 1x6",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_1_6},     0,                   0, FLAGS, "out" },
91     {        "sg", "stereographic",                              0, AV_OPT_TYPE_CONST,  {.i64=STEREOGRAPHIC},   0,                   0, FLAGS, "out" },
92     {  "mercator", "mercator",                                   0, AV_OPT_TYPE_CONST,  {.i64=MERCATOR},        0,                   0, FLAGS, "out" },
93     {      "ball", "ball",                                       0, AV_OPT_TYPE_CONST,  {.i64=BALL},            0,                   0, FLAGS, "out" },
94     {    "hammer", "hammer",                                     0, AV_OPT_TYPE_CONST,  {.i64=HAMMER},          0,                   0, FLAGS, "out" },
95     {"sinusoidal", "sinusoidal",                                 0, AV_OPT_TYPE_CONST,  {.i64=SINUSOIDAL},      0,                   0, FLAGS, "out" },
96     {   "fisheye", "fisheye",                                    0, AV_OPT_TYPE_CONST,  {.i64=FISHEYE},         0,                   0, FLAGS, "out" },
97     {   "pannini", "pannini",                                    0, AV_OPT_TYPE_CONST,  {.i64=PANNINI},         0,                   0, FLAGS, "out" },
98     {"cylindrical", "cylindrical",                               0, AV_OPT_TYPE_CONST,  {.i64=CYLINDRICAL},     0,                   0, FLAGS, "out" },
99     {"perspective", "perspective",                               0, AV_OPT_TYPE_CONST,  {.i64=PERSPECTIVE},     0,                   0, FLAGS, "out" },
100     {"tetrahedron", "tetrahedron",                               0, AV_OPT_TYPE_CONST,  {.i64=TETRAHEDRON},     0,                   0, FLAGS, "out" },
101     {    "interp", "set interpolation method",      OFFSET(interp), AV_OPT_TYPE_INT,    {.i64=BILINEAR},        0, NB_INTERP_METHODS-1, FLAGS, "interp" },
102     {      "near", "nearest neighbour",                          0, AV_OPT_TYPE_CONST,  {.i64=NEAREST},         0,                   0, FLAGS, "interp" },
103     {   "nearest", "nearest neighbour",                          0, AV_OPT_TYPE_CONST,  {.i64=NEAREST},         0,                   0, FLAGS, "interp" },
104     {      "line", "bilinear interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=BILINEAR},        0,                   0, FLAGS, "interp" },
105     {    "linear", "bilinear interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=BILINEAR},        0,                   0, FLAGS, "interp" },
106     {      "cube", "bicubic interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=BICUBIC},         0,                   0, FLAGS, "interp" },
107     {     "cubic", "bicubic interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=BICUBIC},         0,                   0, FLAGS, "interp" },
108     {      "lanc", "lanczos interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=LANCZOS},         0,                   0, FLAGS, "interp" },
109     {   "lanczos", "lanczos interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=LANCZOS},         0,                   0, FLAGS, "interp" },
110     {      "sp16", "spline16 interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=SPLINE16},        0,                   0, FLAGS, "interp" },
111     {  "spline16", "spline16 interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=SPLINE16},        0,                   0, FLAGS, "interp" },
112     {     "gauss", "gaussian interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=GAUSSIAN},        0,                   0, FLAGS, "interp" },
113     {  "gaussian", "gaussian interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=GAUSSIAN},        0,                   0, FLAGS, "interp" },
114     {         "w", "output width",                   OFFSET(width), AV_OPT_TYPE_INT,    {.i64=0},               0,           INT16_MAX, FLAGS, "w"},
115     {         "h", "output height",                 OFFSET(height), AV_OPT_TYPE_INT,    {.i64=0},               0,           INT16_MAX, FLAGS, "h"},
116     { "in_stereo", "input stereo format",        OFFSET(in_stereo), AV_OPT_TYPE_INT,    {.i64=STEREO_2D},       0,    NB_STEREO_FMTS-1, FLAGS, "stereo" },
117     {"out_stereo", "output stereo format",      OFFSET(out_stereo), AV_OPT_TYPE_INT,    {.i64=STEREO_2D},       0,    NB_STEREO_FMTS-1, FLAGS, "stereo" },
118     {        "2d", "2d mono",                                    0, AV_OPT_TYPE_CONST,  {.i64=STEREO_2D},       0,                   0, FLAGS, "stereo" },
119     {       "sbs", "side by side",                               0, AV_OPT_TYPE_CONST,  {.i64=STEREO_SBS},      0,                   0, FLAGS, "stereo" },
120     {        "tb", "top bottom",                                 0, AV_OPT_TYPE_CONST,  {.i64=STEREO_TB},       0,                   0, FLAGS, "stereo" },
121     { "in_forder", "input cubemap face order",   OFFSET(in_forder), AV_OPT_TYPE_STRING, {.str="rludfb"},        0,     NB_DIRECTIONS-1, FLAGS, "in_forder"},
122     {"out_forder", "output cubemap face order", OFFSET(out_forder), AV_OPT_TYPE_STRING, {.str="rludfb"},        0,     NB_DIRECTIONS-1, FLAGS, "out_forder"},
123     {   "in_frot", "input cubemap face rotation",  OFFSET(in_frot), AV_OPT_TYPE_STRING, {.str="000000"},        0,     NB_DIRECTIONS-1, FLAGS, "in_frot"},
124     {  "out_frot", "output cubemap face rotation",OFFSET(out_frot), AV_OPT_TYPE_STRING, {.str="000000"},        0,     NB_DIRECTIONS-1, FLAGS, "out_frot"},
125     {    "in_pad", "percent input cubemap pads",    OFFSET(in_pad), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},           0.f,                 1.f, FLAGS, "in_pad"},
126     {   "out_pad", "percent output cubemap pads",  OFFSET(out_pad), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},           0.f,                 1.f, FLAGS, "out_pad"},
127     {   "fin_pad", "fixed input cubemap pads",     OFFSET(fin_pad), AV_OPT_TYPE_INT,    {.i64=0},               0,                 100, FLAGS, "fin_pad"},
128     {  "fout_pad", "fixed output cubemap pads",   OFFSET(fout_pad), AV_OPT_TYPE_INT,    {.i64=0},               0,                 100, FLAGS, "fout_pad"},
129     {       "yaw", "yaw rotation",                     OFFSET(yaw), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},        -180.f,               180.f, FLAGS, "yaw"},
130     {     "pitch", "pitch rotation",                 OFFSET(pitch), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},        -180.f,               180.f, FLAGS, "pitch"},
131     {      "roll", "roll rotation",                   OFFSET(roll), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},        -180.f,               180.f, FLAGS, "roll"},
132     {    "rorder", "rotation order",                OFFSET(rorder), AV_OPT_TYPE_STRING, {.str="ypr"},           0,                   0, FLAGS, "rorder"},
133     {     "h_fov", "horizontal field of view",       OFFSET(h_fov), AV_OPT_TYPE_FLOAT,  {.dbl=90.f},     0.00001f,               360.f, FLAGS, "h_fov"},
134     {     "v_fov", "vertical field of view",         OFFSET(v_fov), AV_OPT_TYPE_FLOAT,  {.dbl=45.f},     0.00001f,               360.f, FLAGS, "v_fov"},
135     {     "d_fov", "diagonal field of view",         OFFSET(d_fov), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},           0.f,               360.f, FLAGS, "d_fov"},
136     {    "h_flip", "flip out video horizontally",   OFFSET(h_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "h_flip"},
137     {    "v_flip", "flip out video vertically",     OFFSET(v_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "v_flip"},
138     {    "d_flip", "flip out video indepth",        OFFSET(d_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "d_flip"},
139     {   "ih_flip", "flip in video horizontally",   OFFSET(ih_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "ih_flip"},
140     {   "iv_flip", "flip in video vertically",     OFFSET(iv_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "iv_flip"},
141     {  "in_trans", "transpose video input",   OFFSET(in_transpose), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "in_transpose"},
142     { "out_trans", "transpose video output", OFFSET(out_transpose), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "out_transpose"},
143     {    "ih_fov", "input horizontal field of view",OFFSET(ih_fov), AV_OPT_TYPE_FLOAT,  {.dbl=90.f},     0.00001f,               360.f, FLAGS, "ih_fov"},
144     {    "iv_fov", "input vertical field of view",  OFFSET(iv_fov), AV_OPT_TYPE_FLOAT,  {.dbl=45.f},     0.00001f,               360.f, FLAGS, "iv_fov"},
145     {    "id_fov", "input diagonal field of view",  OFFSET(id_fov), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},           0.f,               360.f, FLAGS, "id_fov"},
146     {"alpha_mask", "build mask in alpha plane",      OFFSET(alpha), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "alpha"},
147     { NULL }
148 };
149
150 AVFILTER_DEFINE_CLASS(v360);
151
152 static int query_formats(AVFilterContext *ctx)
153 {
154     V360Context *s = ctx->priv;
155     static const enum AVPixelFormat pix_fmts[] = {
156         // YUVA444
157         AV_PIX_FMT_YUVA444P,   AV_PIX_FMT_YUVA444P9,
158         AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12,
159         AV_PIX_FMT_YUVA444P16,
160
161         // YUVA422
162         AV_PIX_FMT_YUVA422P,   AV_PIX_FMT_YUVA422P9,
163         AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA422P12,
164         AV_PIX_FMT_YUVA422P16,
165
166         // YUVA420
167         AV_PIX_FMT_YUVA420P,   AV_PIX_FMT_YUVA420P9,
168         AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA420P16,
169
170         // YUVJ
171         AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
172         AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
173         AV_PIX_FMT_YUVJ411P,
174
175         // YUV444
176         AV_PIX_FMT_YUV444P,   AV_PIX_FMT_YUV444P9,
177         AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV444P12,
178         AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV444P16,
179
180         // YUV440
181         AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV440P10,
182         AV_PIX_FMT_YUV440P12,
183
184         // YUV422
185         AV_PIX_FMT_YUV422P,   AV_PIX_FMT_YUV422P9,
186         AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV422P12,
187         AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV422P16,
188
189         // YUV420
190         AV_PIX_FMT_YUV420P,   AV_PIX_FMT_YUV420P9,
191         AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV420P12,
192         AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV420P16,
193
194         // YUV411
195         AV_PIX_FMT_YUV411P,
196
197         // YUV410
198         AV_PIX_FMT_YUV410P,
199
200         // GBR
201         AV_PIX_FMT_GBRP,   AV_PIX_FMT_GBRP9,
202         AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12,
203         AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
204
205         // GBRA
206         AV_PIX_FMT_GBRAP,   AV_PIX_FMT_GBRAP10,
207         AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
208
209         // GRAY
210         AV_PIX_FMT_GRAY8,  AV_PIX_FMT_GRAY9,
211         AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12,
212         AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16,
213
214         AV_PIX_FMT_NONE
215     };
216     static const enum AVPixelFormat alpha_pix_fmts[] = {
217         AV_PIX_FMT_YUVA444P,   AV_PIX_FMT_YUVA444P9,
218         AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12,
219         AV_PIX_FMT_YUVA444P16,
220         AV_PIX_FMT_YUVA422P,   AV_PIX_FMT_YUVA422P9,
221         AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA422P12,
222         AV_PIX_FMT_YUVA422P16,
223         AV_PIX_FMT_YUVA420P,   AV_PIX_FMT_YUVA420P9,
224         AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA420P16,
225         AV_PIX_FMT_GBRAP,   AV_PIX_FMT_GBRAP10,
226         AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
227         AV_PIX_FMT_NONE
228     };
229
230     AVFilterFormats *fmts_list = ff_make_format_list(s->alpha ? alpha_pix_fmts : pix_fmts);
231     if (!fmts_list)
232         return AVERROR(ENOMEM);
233     return ff_set_common_formats(ctx, fmts_list);
234 }
235
236 #define DEFINE_REMAP1_LINE(bits, div)                                                    \
237 static void remap1_##bits##bit_line_c(uint8_t *dst, int width, const uint8_t *const src, \
238                                       ptrdiff_t in_linesize,                             \
239                                       const int16_t *const u, const int16_t *const v,    \
240                                       const int16_t *const ker)                          \
241 {                                                                                        \
242     const uint##bits##_t *const s = (const uint##bits##_t *const)src;                    \
243     uint##bits##_t *d = (uint##bits##_t *)dst;                                           \
244                                                                                          \
245     in_linesize /= div;                                                                  \
246                                                                                          \
247     for (int x = 0; x < width; x++)                                                      \
248         d[x] = s[v[x] * in_linesize + u[x]];                                             \
249 }
250
251 DEFINE_REMAP1_LINE( 8, 1)
252 DEFINE_REMAP1_LINE(16, 2)
253
254 /**
255  * Generate remapping function with a given window size and pixel depth.
256  *
257  * @param ws size of interpolation window
258  * @param bits number of bits per pixel
259  */
260 #define DEFINE_REMAP(ws, bits)                                                                             \
261 static int remap##ws##_##bits##bit_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)          \
262 {                                                                                                          \
263     ThreadData *td = arg;                                                                                  \
264     const V360Context *s = ctx->priv;                                                                      \
265     const AVFrame *in = td->in;                                                                            \
266     AVFrame *out = td->out;                                                                                \
267                                                                                                            \
268     for (int stereo = 0; stereo < 1 + s->out_stereo > STEREO_2D; stereo++) {                               \
269         for (int plane = 0; plane < s->nb_planes; plane++) {                                               \
270             const unsigned map = s->map[plane];                                                            \
271             const int in_linesize  = in->linesize[plane];                                                  \
272             const int out_linesize = out->linesize[plane];                                                 \
273             const int uv_linesize = s->uv_linesize[plane];                                                 \
274             const int in_offset_w = stereo ? s->in_offset_w[plane] : 0;                                    \
275             const int in_offset_h = stereo ? s->in_offset_h[plane] : 0;                                    \
276             const int out_offset_w = stereo ? s->out_offset_w[plane] : 0;                                  \
277             const int out_offset_h = stereo ? s->out_offset_h[plane] : 0;                                  \
278             const uint8_t *const src = in->data[plane] +                                                   \
279                                                    in_offset_h * in_linesize + in_offset_w * (bits >> 3);  \
280             uint8_t *dst = out->data[plane] + out_offset_h * out_linesize + out_offset_w * (bits >> 3);    \
281             const uint8_t *mask = plane == 3 ? s->mask : NULL;                                             \
282             const int width = s->pr_width[plane];                                                          \
283             const int height = s->pr_height[plane];                                                        \
284                                                                                                            \
285             const int slice_start = (height *  jobnr     ) / nb_jobs;                                      \
286             const int slice_end   = (height * (jobnr + 1)) / nb_jobs;                                      \
287                                                                                                            \
288             for (int y = slice_start; y < slice_end && !mask; y++) {                                       \
289                 const int16_t *const u = s->u[map] + y * uv_linesize * ws * ws;                            \
290                 const int16_t *const v = s->v[map] + y * uv_linesize * ws * ws;                            \
291                 const int16_t *const ker = s->ker[map] + y * uv_linesize * ws * ws;                        \
292                                                                                                            \
293                 s->remap_line(dst + y * out_linesize, width, src, in_linesize, u, v, ker);                 \
294             }                                                                                              \
295                                                                                                            \
296             for (int y = slice_start; y < slice_end && mask; y++) {                                        \
297                 memcpy(dst + y * out_linesize, mask + y * width * (bits >> 3), width * (bits >> 3));       \
298             }                                                                                              \
299         }                                                                                                  \
300     }                                                                                                      \
301                                                                                                            \
302     return 0;                                                                                              \
303 }
304
305 DEFINE_REMAP(1,  8)
306 DEFINE_REMAP(2,  8)
307 DEFINE_REMAP(4,  8)
308 DEFINE_REMAP(1, 16)
309 DEFINE_REMAP(2, 16)
310 DEFINE_REMAP(4, 16)
311
312 #define DEFINE_REMAP_LINE(ws, bits, div)                                                      \
313 static void remap##ws##_##bits##bit_line_c(uint8_t *dst, int width, const uint8_t *const src, \
314                                            ptrdiff_t in_linesize,                             \
315                                            const int16_t *const u, const int16_t *const v,    \
316                                            const int16_t *const ker)                          \
317 {                                                                                             \
318     const uint##bits##_t *const s = (const uint##bits##_t *const)src;                         \
319     uint##bits##_t *d = (uint##bits##_t *)dst;                                                \
320                                                                                               \
321     in_linesize /= div;                                                                       \
322                                                                                               \
323     for (int x = 0; x < width; x++) {                                                         \
324         const int16_t *const uu = u + x * ws * ws;                                            \
325         const int16_t *const vv = v + x * ws * ws;                                            \
326         const int16_t *const kker = ker + x * ws * ws;                                        \
327         int tmp = 0;                                                                          \
328                                                                                               \
329         for (int i = 0; i < ws; i++) {                                                        \
330             for (int j = 0; j < ws; j++) {                                                    \
331                 tmp += kker[i * ws + j] * s[vv[i * ws + j] * in_linesize + uu[i * ws + j]];   \
332             }                                                                                 \
333         }                                                                                     \
334                                                                                               \
335         d[x] = av_clip_uint##bits(tmp >> 14);                                                 \
336     }                                                                                         \
337 }
338
339 DEFINE_REMAP_LINE(2,  8, 1)
340 DEFINE_REMAP_LINE(4,  8, 1)
341 DEFINE_REMAP_LINE(2, 16, 2)
342 DEFINE_REMAP_LINE(4, 16, 2)
343
344 void ff_v360_init(V360Context *s, int depth)
345 {
346     switch (s->interp) {
347     case NEAREST:
348         s->remap_line = depth <= 8 ? remap1_8bit_line_c : remap1_16bit_line_c;
349         break;
350     case BILINEAR:
351         s->remap_line = depth <= 8 ? remap2_8bit_line_c : remap2_16bit_line_c;
352         break;
353     case BICUBIC:
354     case LANCZOS:
355     case SPLINE16:
356     case GAUSSIAN:
357         s->remap_line = depth <= 8 ? remap4_8bit_line_c : remap4_16bit_line_c;
358         break;
359     }
360
361     if (ARCH_X86)
362         ff_v360_init_x86(s, depth);
363 }
364
365 /**
366  * Save nearest pixel coordinates for remapping.
367  *
368  * @param du horizontal relative coordinate
369  * @param dv vertical relative coordinate
370  * @param rmap calculated 4x4 window
371  * @param u u remap data
372  * @param v v remap data
373  * @param ker ker remap data
374  */
375 static void nearest_kernel(float du, float dv, const XYRemap *rmap,
376                            int16_t *u, int16_t *v, int16_t *ker)
377 {
378     const int i = lrintf(dv) + 1;
379     const int j = lrintf(du) + 1;
380
381     u[0] = rmap->u[i][j];
382     v[0] = rmap->v[i][j];
383 }
384
385 /**
386  * Calculate kernel for bilinear interpolation.
387  *
388  * @param du horizontal relative coordinate
389  * @param dv vertical relative coordinate
390  * @param rmap calculated 4x4 window
391  * @param u u remap data
392  * @param v v remap data
393  * @param ker ker remap data
394  */
395 static void bilinear_kernel(float du, float dv, const XYRemap *rmap,
396                             int16_t *u, int16_t *v, int16_t *ker)
397 {
398     for (int i = 0; i < 2; i++) {
399         for (int j = 0; j < 2; j++) {
400             u[i * 2 + j] = rmap->u[i + 1][j + 1];
401             v[i * 2 + j] = rmap->v[i + 1][j + 1];
402         }
403     }
404
405     ker[0] = lrintf((1.f - du) * (1.f - dv) * 16385.f);
406     ker[1] = lrintf(       du  * (1.f - dv) * 16385.f);
407     ker[2] = lrintf((1.f - du) *        dv  * 16385.f);
408     ker[3] = lrintf(       du  *        dv  * 16385.f);
409 }
410
411 /**
412  * Calculate 1-dimensional cubic coefficients.
413  *
414  * @param t relative coordinate
415  * @param coeffs coefficients
416  */
417 static inline void calculate_bicubic_coeffs(float t, float *coeffs)
418 {
419     const float tt  = t * t;
420     const float ttt = t * t * t;
421
422     coeffs[0] =     - t / 3.f + tt / 2.f - ttt / 6.f;
423     coeffs[1] = 1.f - t / 2.f - tt       + ttt / 2.f;
424     coeffs[2] =       t       + tt / 2.f - ttt / 2.f;
425     coeffs[3] =     - t / 6.f            + ttt / 6.f;
426 }
427
428 /**
429  * Calculate kernel for bicubic interpolation.
430  *
431  * @param du horizontal relative coordinate
432  * @param dv vertical relative coordinate
433  * @param rmap calculated 4x4 window
434  * @param u u remap data
435  * @param v v remap data
436  * @param ker ker remap data
437  */
438 static void bicubic_kernel(float du, float dv, const XYRemap *rmap,
439                            int16_t *u, int16_t *v, int16_t *ker)
440 {
441     float du_coeffs[4];
442     float dv_coeffs[4];
443
444     calculate_bicubic_coeffs(du, du_coeffs);
445     calculate_bicubic_coeffs(dv, dv_coeffs);
446
447     for (int i = 0; i < 4; i++) {
448         for (int j = 0; j < 4; j++) {
449             u[i * 4 + j] = rmap->u[i][j];
450             v[i * 4 + j] = rmap->v[i][j];
451             ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
452         }
453     }
454 }
455
456 /**
457  * Calculate 1-dimensional lanczos coefficients.
458  *
459  * @param t relative coordinate
460  * @param coeffs coefficients
461  */
462 static inline void calculate_lanczos_coeffs(float t, float *coeffs)
463 {
464     float sum = 0.f;
465
466     for (int i = 0; i < 4; i++) {
467         const float x = M_PI * (t - i + 1);
468         if (x == 0.f) {
469             coeffs[i] = 1.f;
470         } else {
471             coeffs[i] = sinf(x) * sinf(x / 2.f) / (x * x / 2.f);
472         }
473         sum += coeffs[i];
474     }
475
476     for (int i = 0; i < 4; i++) {
477         coeffs[i] /= sum;
478     }
479 }
480
481 /**
482  * Calculate kernel for lanczos interpolation.
483  *
484  * @param du horizontal relative coordinate
485  * @param dv vertical relative coordinate
486  * @param rmap calculated 4x4 window
487  * @param u u remap data
488  * @param v v remap data
489  * @param ker ker remap data
490  */
491 static void lanczos_kernel(float du, float dv, const XYRemap *rmap,
492                            int16_t *u, int16_t *v, int16_t *ker)
493 {
494     float du_coeffs[4];
495     float dv_coeffs[4];
496
497     calculate_lanczos_coeffs(du, du_coeffs);
498     calculate_lanczos_coeffs(dv, dv_coeffs);
499
500     for (int i = 0; i < 4; i++) {
501         for (int j = 0; j < 4; j++) {
502             u[i * 4 + j] = rmap->u[i][j];
503             v[i * 4 + j] = rmap->v[i][j];
504             ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
505         }
506     }
507 }
508
509 /**
510  * Calculate 1-dimensional spline16 coefficients.
511  *
512  * @param t relative coordinate
513  * @param coeffs coefficients
514  */
515 static void calculate_spline16_coeffs(float t, float *coeffs)
516 {
517     coeffs[0] = ((-1.f / 3.f * t + 0.8f) * t - 7.f / 15.f) * t;
518     coeffs[1] = ((t - 9.f / 5.f) * t - 0.2f) * t + 1.f;
519     coeffs[2] = ((6.f / 5.f - t) * t + 0.8f) * t;
520     coeffs[3] = ((1.f / 3.f * t - 0.2f) * t - 2.f / 15.f) * t;
521 }
522
523 /**
524  * Calculate kernel for spline16 interpolation.
525  *
526  * @param du horizontal relative coordinate
527  * @param dv vertical relative coordinate
528  * @param rmap calculated 4x4 window
529  * @param u u remap data
530  * @param v v remap data
531  * @param ker ker remap data
532  */
533 static void spline16_kernel(float du, float dv, const XYRemap *rmap,
534                             int16_t *u, int16_t *v, int16_t *ker)
535 {
536     float du_coeffs[4];
537     float dv_coeffs[4];
538
539     calculate_spline16_coeffs(du, du_coeffs);
540     calculate_spline16_coeffs(dv, dv_coeffs);
541
542     for (int i = 0; i < 4; i++) {
543         for (int j = 0; j < 4; j++) {
544             u[i * 4 + j] = rmap->u[i][j];
545             v[i * 4 + j] = rmap->v[i][j];
546             ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
547         }
548     }
549 }
550
551 /**
552  * Calculate 1-dimensional gaussian coefficients.
553  *
554  * @param t relative coordinate
555  * @param coeffs coefficients
556  */
557 static void calculate_gaussian_coeffs(float t, float *coeffs)
558 {
559     float sum = 0.f;
560
561     for (int i = 0; i < 4; i++) {
562         const float x = t - (i - 1);
563         if (x == 0.f) {
564             coeffs[i] = 1.f;
565         } else {
566             coeffs[i] = expf(-2.f * x * x) * expf(-x * x / 2.f);
567         }
568         sum += coeffs[i];
569     }
570
571     for (int i = 0; i < 4; i++) {
572         coeffs[i] /= sum;
573     }
574 }
575
576 /**
577  * Calculate kernel for gaussian interpolation.
578  *
579  * @param du horizontal relative coordinate
580  * @param dv vertical relative coordinate
581  * @param rmap calculated 4x4 window
582  * @param u u remap data
583  * @param v v remap data
584  * @param ker ker remap data
585  */
586 static void gaussian_kernel(float du, float dv, const XYRemap *rmap,
587                             int16_t *u, int16_t *v, int16_t *ker)
588 {
589     float du_coeffs[4];
590     float dv_coeffs[4];
591
592     calculate_gaussian_coeffs(du, du_coeffs);
593     calculate_gaussian_coeffs(dv, dv_coeffs);
594
595     for (int i = 0; i < 4; i++) {
596         for (int j = 0; j < 4; j++) {
597             u[i * 4 + j] = rmap->u[i][j];
598             v[i * 4 + j] = rmap->v[i][j];
599             ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
600         }
601     }
602 }
603
604 /**
605  * Modulo operation with only positive remainders.
606  *
607  * @param a dividend
608  * @param b divisor
609  *
610  * @return positive remainder of (a / b)
611  */
612 static inline int mod(int a, int b)
613 {
614     const int res = a % b;
615     if (res < 0) {
616         return res + b;
617     } else {
618         return res;
619     }
620 }
621
622 /**
623  * Convert char to corresponding direction.
624  * Used for cubemap options.
625  */
626 static int get_direction(char c)
627 {
628     switch (c) {
629     case 'r':
630         return RIGHT;
631     case 'l':
632         return LEFT;
633     case 'u':
634         return UP;
635     case 'd':
636         return DOWN;
637     case 'f':
638         return FRONT;
639     case 'b':
640         return BACK;
641     default:
642         return -1;
643     }
644 }
645
646 /**
647  * Convert char to corresponding rotation angle.
648  * Used for cubemap options.
649  */
650 static int get_rotation(char c)
651 {
652     switch (c) {
653     case '0':
654         return ROT_0;
655     case '1':
656         return ROT_90;
657     case '2':
658         return ROT_180;
659     case '3':
660         return ROT_270;
661     default:
662         return -1;
663     }
664 }
665
666 /**
667  * Convert char to corresponding rotation order.
668  */
669 static int get_rorder(char c)
670 {
671     switch (c) {
672     case 'Y':
673     case 'y':
674         return YAW;
675     case 'P':
676     case 'p':
677         return PITCH;
678     case 'R':
679     case 'r':
680         return ROLL;
681     default:
682         return -1;
683     }
684 }
685
686 /**
687  * Prepare data for processing cubemap input format.
688  *
689  * @param ctx filter context
690  *
691  * @return error code
692  */
693 static int prepare_cube_in(AVFilterContext *ctx)
694 {
695     V360Context *s = ctx->priv;
696
697     for (int face = 0; face < NB_FACES; face++) {
698         const char c = s->in_forder[face];
699         int direction;
700
701         if (c == '\0') {
702             av_log(ctx, AV_LOG_ERROR,
703                    "Incomplete in_forder option. Direction for all 6 faces should be specified.\n");
704             return AVERROR(EINVAL);
705         }
706
707         direction = get_direction(c);
708         if (direction == -1) {
709             av_log(ctx, AV_LOG_ERROR,
710                    "Incorrect direction symbol '%c' in in_forder option.\n", c);
711             return AVERROR(EINVAL);
712         }
713
714         s->in_cubemap_face_order[direction] = face;
715     }
716
717     for (int face = 0; face < NB_FACES; face++) {
718         const char c = s->in_frot[face];
719         int rotation;
720
721         if (c == '\0') {
722             av_log(ctx, AV_LOG_ERROR,
723                    "Incomplete in_frot option. Rotation for all 6 faces should be specified.\n");
724             return AVERROR(EINVAL);
725         }
726
727         rotation = get_rotation(c);
728         if (rotation == -1) {
729             av_log(ctx, AV_LOG_ERROR,
730                    "Incorrect rotation symbol '%c' in in_frot option.\n", c);
731             return AVERROR(EINVAL);
732         }
733
734         s->in_cubemap_face_rotation[face] = rotation;
735     }
736
737     return 0;
738 }
739
740 /**
741  * Prepare data for processing cubemap output format.
742  *
743  * @param ctx filter context
744  *
745  * @return error code
746  */
747 static int prepare_cube_out(AVFilterContext *ctx)
748 {
749     V360Context *s = ctx->priv;
750
751     for (int face = 0; face < NB_FACES; face++) {
752         const char c = s->out_forder[face];
753         int direction;
754
755         if (c == '\0') {
756             av_log(ctx, AV_LOG_ERROR,
757                    "Incomplete out_forder option. Direction for all 6 faces should be specified.\n");
758             return AVERROR(EINVAL);
759         }
760
761         direction = get_direction(c);
762         if (direction == -1) {
763             av_log(ctx, AV_LOG_ERROR,
764                    "Incorrect direction symbol '%c' in out_forder option.\n", c);
765             return AVERROR(EINVAL);
766         }
767
768         s->out_cubemap_direction_order[face] = direction;
769     }
770
771     for (int face = 0; face < NB_FACES; face++) {
772         const char c = s->out_frot[face];
773         int rotation;
774
775         if (c == '\0') {
776             av_log(ctx, AV_LOG_ERROR,
777                    "Incomplete out_frot option. Rotation for all 6 faces should be specified.\n");
778             return AVERROR(EINVAL);
779         }
780
781         rotation = get_rotation(c);
782         if (rotation == -1) {
783             av_log(ctx, AV_LOG_ERROR,
784                    "Incorrect rotation symbol '%c' in out_frot option.\n", c);
785             return AVERROR(EINVAL);
786         }
787
788         s->out_cubemap_face_rotation[face] = rotation;
789     }
790
791     return 0;
792 }
793
794 static inline void rotate_cube_face(float *uf, float *vf, int rotation)
795 {
796     float tmp;
797
798     switch (rotation) {
799     case ROT_0:
800         break;
801     case ROT_90:
802         tmp =  *uf;
803         *uf = -*vf;
804         *vf =  tmp;
805         break;
806     case ROT_180:
807         *uf = -*uf;
808         *vf = -*vf;
809         break;
810     case ROT_270:
811         tmp = -*uf;
812         *uf =  *vf;
813         *vf =  tmp;
814         break;
815     default:
816         av_assert0(0);
817     }
818 }
819
820 static inline void rotate_cube_face_inverse(float *uf, float *vf, int rotation)
821 {
822     float tmp;
823
824     switch (rotation) {
825     case ROT_0:
826         break;
827     case ROT_90:
828         tmp = -*uf;
829         *uf =  *vf;
830         *vf =  tmp;
831         break;
832     case ROT_180:
833         *uf = -*uf;
834         *vf = -*vf;
835         break;
836     case ROT_270:
837         tmp =  *uf;
838         *uf = -*vf;
839         *vf =  tmp;
840         break;
841     default:
842         av_assert0(0);
843     }
844 }
845
846 /**
847  * Normalize vector.
848  *
849  * @param vec vector
850  */
851 static void normalize_vector(float *vec)
852 {
853     const float norm = sqrtf(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
854
855     vec[0] /= norm;
856     vec[1] /= norm;
857     vec[2] /= norm;
858 }
859
860 /**
861  * Calculate 3D coordinates on sphere for corresponding cubemap position.
862  * Common operation for every cubemap.
863  *
864  * @param s filter private context
865  * @param uf horizontal cubemap coordinate [0, 1)
866  * @param vf vertical cubemap coordinate [0, 1)
867  * @param face face of cubemap
868  * @param vec coordinates on sphere
869  * @param scalew scale for uf
870  * @param scaleh scale for vf
871  */
872 static void cube_to_xyz(const V360Context *s,
873                         float uf, float vf, int face,
874                         float *vec, float scalew, float scaleh)
875 {
876     const int direction = s->out_cubemap_direction_order[face];
877     float l_x, l_y, l_z;
878
879     uf /= scalew;
880     vf /= scaleh;
881
882     rotate_cube_face_inverse(&uf, &vf, s->out_cubemap_face_rotation[face]);
883
884     switch (direction) {
885     case RIGHT:
886         l_x =  1.f;
887         l_y = -vf;
888         l_z =  uf;
889         break;
890     case LEFT:
891         l_x = -1.f;
892         l_y = -vf;
893         l_z = -uf;
894         break;
895     case UP:
896         l_x =  uf;
897         l_y =  1.f;
898         l_z = -vf;
899         break;
900     case DOWN:
901         l_x =  uf;
902         l_y = -1.f;
903         l_z =  vf;
904         break;
905     case FRONT:
906         l_x =  uf;
907         l_y = -vf;
908         l_z = -1.f;
909         break;
910     case BACK:
911         l_x = -uf;
912         l_y = -vf;
913         l_z =  1.f;
914         break;
915     default:
916         av_assert0(0);
917     }
918
919     vec[0] = l_x;
920     vec[1] = l_y;
921     vec[2] = l_z;
922
923     normalize_vector(vec);
924 }
925
926 /**
927  * Calculate cubemap position for corresponding 3D coordinates on sphere.
928  * Common operation for every cubemap.
929  *
930  * @param s filter private context
931  * @param vec coordinated on sphere
932  * @param uf horizontal cubemap coordinate [0, 1)
933  * @param vf vertical cubemap coordinate [0, 1)
934  * @param direction direction of view
935  */
936 static void xyz_to_cube(const V360Context *s,
937                         const float *vec,
938                         float *uf, float *vf, int *direction)
939 {
940     const float phi   = atan2f(vec[0], -vec[2]);
941     const float theta = asinf(-vec[1]);
942     float phi_norm, theta_threshold;
943     int face;
944
945     if (phi >= -M_PI_4 && phi < M_PI_4) {
946         *direction = FRONT;
947         phi_norm = phi;
948     } else if (phi >= -(M_PI_2 + M_PI_4) && phi < -M_PI_4) {
949         *direction = LEFT;
950         phi_norm = phi + M_PI_2;
951     } else if (phi >= M_PI_4 && phi < M_PI_2 + M_PI_4) {
952         *direction = RIGHT;
953         phi_norm = phi - M_PI_2;
954     } else {
955         *direction = BACK;
956         phi_norm = phi + ((phi > 0.f) ? -M_PI : M_PI);
957     }
958
959     theta_threshold = atanf(cosf(phi_norm));
960     if (theta > theta_threshold) {
961         *direction = DOWN;
962     } else if (theta < -theta_threshold) {
963         *direction = UP;
964     }
965
966     switch (*direction) {
967     case RIGHT:
968         *uf =  vec[2] / vec[0];
969         *vf = -vec[1] / vec[0];
970         break;
971     case LEFT:
972         *uf =  vec[2] / vec[0];
973         *vf =  vec[1] / vec[0];
974         break;
975     case UP:
976         *uf =  vec[0] / vec[1];
977         *vf = -vec[2] / vec[1];
978         break;
979     case DOWN:
980         *uf = -vec[0] / vec[1];
981         *vf = -vec[2] / vec[1];
982         break;
983     case FRONT:
984         *uf = -vec[0] / vec[2];
985         *vf =  vec[1] / vec[2];
986         break;
987     case BACK:
988         *uf = -vec[0] / vec[2];
989         *vf = -vec[1] / vec[2];
990         break;
991     default:
992         av_assert0(0);
993     }
994
995     face = s->in_cubemap_face_order[*direction];
996     rotate_cube_face(uf, vf, s->in_cubemap_face_rotation[face]);
997
998     (*uf) *= s->input_mirror_modifier[0];
999     (*vf) *= s->input_mirror_modifier[1];
1000 }
1001
1002 /**
1003  * Find position on another cube face in case of overflow/underflow.
1004  * Used for calculation of interpolation window.
1005  *
1006  * @param s filter private context
1007  * @param uf horizontal cubemap coordinate
1008  * @param vf vertical cubemap coordinate
1009  * @param direction direction of view
1010  * @param new_uf new horizontal cubemap coordinate
1011  * @param new_vf new vertical cubemap coordinate
1012  * @param face face position on cubemap
1013  */
1014 static void process_cube_coordinates(const V360Context *s,
1015                                      float uf, float vf, int direction,
1016                                      float *new_uf, float *new_vf, int *face)
1017 {
1018     /*
1019      *  Cubemap orientation
1020      *
1021      *           width
1022      *         <------->
1023      *         +-------+
1024      *         |       |                              U
1025      *         | up    |                   h       ------->
1026      * +-------+-------+-------+-------+ ^ e      |
1027      * |       |       |       |       | | i    V |
1028      * | left  | front | right | back  | | g      |
1029      * +-------+-------+-------+-------+ v h      v
1030      *         |       |                   t
1031      *         | down  |
1032      *         +-------+
1033      */
1034
1035     *face = s->in_cubemap_face_order[direction];
1036     rotate_cube_face_inverse(&uf, &vf, s->in_cubemap_face_rotation[*face]);
1037
1038     if ((uf < -1.f || uf >= 1.f) && (vf < -1.f || vf >= 1.f)) {
1039         // There are no pixels to use in this case
1040         *new_uf = uf;
1041         *new_vf = vf;
1042     } else if (uf < -1.f) {
1043         uf += 2.f;
1044         switch (direction) {
1045         case RIGHT:
1046             direction = FRONT;
1047             *new_uf =  uf;
1048             *new_vf =  vf;
1049             break;
1050         case LEFT:
1051             direction = BACK;
1052             *new_uf =  uf;
1053             *new_vf =  vf;
1054             break;
1055         case UP:
1056             direction = LEFT;
1057             *new_uf =  vf;
1058             *new_vf = -uf;
1059             break;
1060         case DOWN:
1061             direction = LEFT;
1062             *new_uf = -vf;
1063             *new_vf =  uf;
1064             break;
1065         case FRONT:
1066             direction = LEFT;
1067             *new_uf =  uf;
1068             *new_vf =  vf;
1069             break;
1070         case BACK:
1071             direction = RIGHT;
1072             *new_uf =  uf;
1073             *new_vf =  vf;
1074             break;
1075         default:
1076             av_assert0(0);
1077         }
1078     } else if (uf >= 1.f) {
1079         uf -= 2.f;
1080         switch (direction) {
1081         case RIGHT:
1082             direction = BACK;
1083             *new_uf =  uf;
1084             *new_vf =  vf;
1085             break;
1086         case LEFT:
1087             direction = FRONT;
1088             *new_uf =  uf;
1089             *new_vf =  vf;
1090             break;
1091         case UP:
1092             direction = RIGHT;
1093             *new_uf = -vf;
1094             *new_vf =  uf;
1095             break;
1096         case DOWN:
1097             direction = RIGHT;
1098             *new_uf =  vf;
1099             *new_vf = -uf;
1100             break;
1101         case FRONT:
1102             direction = RIGHT;
1103             *new_uf =  uf;
1104             *new_vf =  vf;
1105             break;
1106         case BACK:
1107             direction = LEFT;
1108             *new_uf =  uf;
1109             *new_vf =  vf;
1110             break;
1111         default:
1112             av_assert0(0);
1113         }
1114     } else if (vf < -1.f) {
1115         vf += 2.f;
1116         switch (direction) {
1117         case RIGHT:
1118             direction = UP;
1119             *new_uf =  vf;
1120             *new_vf = -uf;
1121             break;
1122         case LEFT:
1123             direction = UP;
1124             *new_uf = -vf;
1125             *new_vf =  uf;
1126             break;
1127         case UP:
1128             direction = BACK;
1129             *new_uf = -uf;
1130             *new_vf = -vf;
1131             break;
1132         case DOWN:
1133             direction = FRONT;
1134             *new_uf =  uf;
1135             *new_vf =  vf;
1136             break;
1137         case FRONT:
1138             direction = UP;
1139             *new_uf =  uf;
1140             *new_vf =  vf;
1141             break;
1142         case BACK:
1143             direction = UP;
1144             *new_uf = -uf;
1145             *new_vf = -vf;
1146             break;
1147         default:
1148             av_assert0(0);
1149         }
1150     } else if (vf >= 1.f) {
1151         vf -= 2.f;
1152         switch (direction) {
1153         case RIGHT:
1154             direction = DOWN;
1155             *new_uf = -vf;
1156             *new_vf =  uf;
1157             break;
1158         case LEFT:
1159             direction = DOWN;
1160             *new_uf =  vf;
1161             *new_vf = -uf;
1162             break;
1163         case UP:
1164             direction = FRONT;
1165             *new_uf =  uf;
1166             *new_vf =  vf;
1167             break;
1168         case DOWN:
1169             direction = BACK;
1170             *new_uf = -uf;
1171             *new_vf = -vf;
1172             break;
1173         case FRONT:
1174             direction = DOWN;
1175             *new_uf =  uf;
1176             *new_vf =  vf;
1177             break;
1178         case BACK:
1179             direction = DOWN;
1180             *new_uf = -uf;
1181             *new_vf = -vf;
1182             break;
1183         default:
1184             av_assert0(0);
1185         }
1186     } else {
1187         // Inside cube face
1188         *new_uf = uf;
1189         *new_vf = vf;
1190     }
1191
1192     *face = s->in_cubemap_face_order[direction];
1193     rotate_cube_face(new_uf, new_vf, s->in_cubemap_face_rotation[*face]);
1194 }
1195
1196 /**
1197  * Calculate 3D coordinates on sphere for corresponding frame position in cubemap3x2 format.
1198  *
1199  * @param s filter private context
1200  * @param i horizontal position on frame [0, width)
1201  * @param j vertical position on frame [0, height)
1202  * @param width frame width
1203  * @param height frame height
1204  * @param vec coordinates on sphere
1205  */
1206 static int cube3x2_to_xyz(const V360Context *s,
1207                           int i, int j, int width, int height,
1208                           float *vec)
1209 {
1210     const float scalew = s->fout_pad > 0 ? 1.f - s->fout_pad / (s->out_width  / 3.f) : 1.f - s->out_pad;
1211     const float scaleh = s->fout_pad > 0 ? 1.f - s->fout_pad / (s->out_height / 2.f) : 1.f - s->out_pad;
1212
1213     const float ew = width  / 3.f;
1214     const float eh = height / 2.f;
1215
1216     const int u_face = floorf(i / ew);
1217     const int v_face = floorf(j / eh);
1218     const int face = u_face + 3 * v_face;
1219
1220     const int u_shift = ceilf(ew * u_face);
1221     const int v_shift = ceilf(eh * v_face);
1222     const int ewi = ceilf(ew * (u_face + 1)) - u_shift;
1223     const int ehi = ceilf(eh * (v_face + 1)) - v_shift;
1224
1225     const float uf = 2.f * (i - u_shift + 0.5f) / ewi - 1.f;
1226     const float vf = 2.f * (j - v_shift + 0.5f) / ehi - 1.f;
1227
1228     cube_to_xyz(s, uf, vf, face, vec, scalew, scaleh);
1229
1230     return 1;
1231 }
1232
1233 /**
1234  * Calculate frame position in cubemap3x2 format for corresponding 3D coordinates on sphere.
1235  *
1236  * @param s filter private context
1237  * @param vec coordinates on sphere
1238  * @param width frame width
1239  * @param height frame height
1240  * @param us horizontal coordinates for interpolation window
1241  * @param vs vertical coordinates for interpolation window
1242  * @param du horizontal relative coordinate
1243  * @param dv vertical relative coordinate
1244  */
1245 static int xyz_to_cube3x2(const V360Context *s,
1246                           const float *vec, int width, int height,
1247                           int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1248 {
1249     const float scalew = s->fin_pad > 0 ? 1.f - s->fin_pad / (s->in_width  / 3.f) : 1.f - s->in_pad;
1250     const float scaleh = s->fin_pad > 0 ? 1.f - s->fin_pad / (s->in_height / 2.f) : 1.f - s->in_pad;
1251     const float ew = width  / 3.f;
1252     const float eh = height / 2.f;
1253     float uf, vf;
1254     int ui, vi;
1255     int ewi, ehi;
1256     int direction, face;
1257     int u_face, v_face;
1258
1259     xyz_to_cube(s, vec, &uf, &vf, &direction);
1260
1261     uf *= scalew;
1262     vf *= scaleh;
1263
1264     face = s->in_cubemap_face_order[direction];
1265     u_face = face % 3;
1266     v_face = face / 3;
1267     ewi = ceilf(ew * (u_face + 1)) - ceilf(ew * u_face);
1268     ehi = ceilf(eh * (v_face + 1)) - ceilf(eh * v_face);
1269
1270     uf = 0.5f * ewi * (uf + 1.f) - 0.5f;
1271     vf = 0.5f * ehi * (vf + 1.f) - 0.5f;
1272
1273     ui = floorf(uf);
1274     vi = floorf(vf);
1275
1276     *du = uf - ui;
1277     *dv = vf - vi;
1278
1279     for (int i = -1; i < 3; i++) {
1280         for (int j = -1; j < 3; j++) {
1281             int new_ui = ui + j;
1282             int new_vi = vi + i;
1283             int u_shift, v_shift;
1284             int new_ewi, new_ehi;
1285
1286             if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1287                 face = s->in_cubemap_face_order[direction];
1288
1289                 u_face = face % 3;
1290                 v_face = face / 3;
1291                 u_shift = ceilf(ew * u_face);
1292                 v_shift = ceilf(eh * v_face);
1293             } else {
1294                 uf = 2.f * new_ui / ewi - 1.f;
1295                 vf = 2.f * new_vi / ehi - 1.f;
1296
1297                 uf /= scalew;
1298                 vf /= scaleh;
1299
1300                 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1301
1302                 uf *= scalew;
1303                 vf *= scaleh;
1304
1305                 u_face = face % 3;
1306                 v_face = face / 3;
1307                 u_shift = ceilf(ew * u_face);
1308                 v_shift = ceilf(eh * v_face);
1309                 new_ewi = ceilf(ew * (u_face + 1)) - u_shift;
1310                 new_ehi = ceilf(eh * (v_face + 1)) - v_shift;
1311
1312                 new_ui = av_clip(lrintf(0.5f * new_ewi * (uf + 1.f)), 0, new_ewi - 1);
1313                 new_vi = av_clip(lrintf(0.5f * new_ehi * (vf + 1.f)), 0, new_ehi - 1);
1314             }
1315
1316             us[i + 1][j + 1] = u_shift + new_ui;
1317             vs[i + 1][j + 1] = v_shift + new_vi;
1318         }
1319     }
1320
1321     return 1;
1322 }
1323
1324 /**
1325  * Calculate 3D coordinates on sphere for corresponding frame position in cubemap1x6 format.
1326  *
1327  * @param s filter private context
1328  * @param i horizontal position on frame [0, width)
1329  * @param j vertical position on frame [0, height)
1330  * @param width frame width
1331  * @param height frame height
1332  * @param vec coordinates on sphere
1333  */
1334 static int cube1x6_to_xyz(const V360Context *s,
1335                           int i, int j, int width, int height,
1336                           float *vec)
1337 {
1338     const float scalew = s->fout_pad > 0 ? 1.f - (float)(s->fout_pad) / s->out_width : 1.f - s->out_pad;
1339     const float scaleh = s->fout_pad > 0 ? 1.f - s->fout_pad / (s->out_height / 6.f) : 1.f - s->out_pad;
1340
1341     const float ew = width;
1342     const float eh = height / 6.f;
1343
1344     const int face = floorf(j / eh);
1345
1346     const int v_shift = ceilf(eh * face);
1347     const int ehi = ceilf(eh * (face + 1)) - v_shift;
1348
1349     const float uf = 2.f * (i           + 0.5f) / ew  - 1.f;
1350     const float vf = 2.f * (j - v_shift + 0.5f) / ehi - 1.f;
1351
1352     cube_to_xyz(s, uf, vf, face, vec, scalew, scaleh);
1353
1354     return 1;
1355 }
1356
1357 /**
1358  * Calculate 3D coordinates on sphere for corresponding frame position in cubemap6x1 format.
1359  *
1360  * @param s filter private context
1361  * @param i horizontal position on frame [0, width)
1362  * @param j vertical position on frame [0, height)
1363  * @param width frame width
1364  * @param height frame height
1365  * @param vec coordinates on sphere
1366  */
1367 static int cube6x1_to_xyz(const V360Context *s,
1368                           int i, int j, int width, int height,
1369                           float *vec)
1370 {
1371     const float scalew = s->fout_pad > 0 ? 1.f - s->fout_pad / (s->out_width / 6.f)   : 1.f - s->out_pad;
1372     const float scaleh = s->fout_pad > 0 ? 1.f - (float)(s->fout_pad) / s->out_height : 1.f - s->out_pad;
1373
1374     const float ew = width / 6.f;
1375     const float eh = height;
1376
1377     const int face = floorf(i / ew);
1378
1379     const int u_shift = ceilf(ew * face);
1380     const int ewi = ceilf(ew * (face + 1)) - u_shift;
1381
1382     const float uf = 2.f * (i - u_shift + 0.5f) / ewi - 1.f;
1383     const float vf = 2.f * (j           + 0.5f) / eh  - 1.f;
1384
1385     cube_to_xyz(s, uf, vf, face, vec, scalew, scaleh);
1386
1387     return 1;
1388 }
1389
1390 /**
1391  * Calculate frame position in cubemap1x6 format for corresponding 3D coordinates on sphere.
1392  *
1393  * @param s filter private context
1394  * @param vec coordinates on sphere
1395  * @param width frame width
1396  * @param height frame height
1397  * @param us horizontal coordinates for interpolation window
1398  * @param vs vertical coordinates for interpolation window
1399  * @param du horizontal relative coordinate
1400  * @param dv vertical relative coordinate
1401  */
1402 static int xyz_to_cube1x6(const V360Context *s,
1403                           const float *vec, int width, int height,
1404                           int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1405 {
1406     const float scalew = s->fin_pad > 0 ? 1.f - (float)(s->fin_pad) / s->in_width : 1.f - s->in_pad;
1407     const float scaleh = s->fin_pad > 0 ? 1.f - s->fin_pad / (s->in_height / 6.f) : 1.f - s->in_pad;
1408     const float eh = height / 6.f;
1409     const int ewi = width;
1410     float uf, vf;
1411     int ui, vi;
1412     int ehi;
1413     int direction, face;
1414
1415     xyz_to_cube(s, vec, &uf, &vf, &direction);
1416
1417     uf *= scalew;
1418     vf *= scaleh;
1419
1420     face = s->in_cubemap_face_order[direction];
1421     ehi = ceilf(eh * (face + 1)) - ceilf(eh * face);
1422
1423     uf = 0.5f * ewi * (uf + 1.f) - 0.5f;
1424     vf = 0.5f * ehi * (vf + 1.f) - 0.5f;
1425
1426     ui = floorf(uf);
1427     vi = floorf(vf);
1428
1429     *du = uf - ui;
1430     *dv = vf - vi;
1431
1432     for (int i = -1; i < 3; i++) {
1433         for (int j = -1; j < 3; j++) {
1434             int new_ui = ui + j;
1435             int new_vi = vi + i;
1436             int v_shift;
1437             int new_ehi;
1438
1439             if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1440                 face = s->in_cubemap_face_order[direction];
1441
1442                 v_shift = ceilf(eh * face);
1443             } else {
1444                 uf = 2.f * new_ui / ewi - 1.f;
1445                 vf = 2.f * new_vi / ehi - 1.f;
1446
1447                 uf /= scalew;
1448                 vf /= scaleh;
1449
1450                 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1451
1452                 uf *= scalew;
1453                 vf *= scaleh;
1454
1455                 v_shift = ceilf(eh * face);
1456                 new_ehi = ceilf(eh * (face + 1)) - v_shift;
1457
1458                 new_ui = av_clip(lrintf(0.5f *     ewi * (uf + 1.f)), 0,     ewi - 1);
1459                 new_vi = av_clip(lrintf(0.5f * new_ehi * (vf + 1.f)), 0, new_ehi - 1);
1460             }
1461
1462             us[i + 1][j + 1] =           new_ui;
1463             vs[i + 1][j + 1] = v_shift + new_vi;
1464         }
1465     }
1466
1467     return 1;
1468 }
1469
1470 /**
1471  * Calculate frame position in cubemap6x1 format for corresponding 3D coordinates on sphere.
1472  *
1473  * @param s filter private context
1474  * @param vec coordinates on sphere
1475  * @param width frame width
1476  * @param height frame height
1477  * @param us horizontal coordinates for interpolation window
1478  * @param vs vertical coordinates for interpolation window
1479  * @param du horizontal relative coordinate
1480  * @param dv vertical relative coordinate
1481  */
1482 static int xyz_to_cube6x1(const V360Context *s,
1483                           const float *vec, int width, int height,
1484                           int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1485 {
1486     const float scalew = s->fin_pad > 0 ? 1.f - s->fin_pad / (s->in_width / 6.f)   : 1.f - s->in_pad;
1487     const float scaleh = s->fin_pad > 0 ? 1.f - (float)(s->fin_pad) / s->in_height : 1.f - s->in_pad;
1488     const float ew = width / 6.f;
1489     const int ehi = height;
1490     float uf, vf;
1491     int ui, vi;
1492     int ewi;
1493     int direction, face;
1494
1495     xyz_to_cube(s, vec, &uf, &vf, &direction);
1496
1497     uf *= scalew;
1498     vf *= scaleh;
1499
1500     face = s->in_cubemap_face_order[direction];
1501     ewi = ceilf(ew * (face + 1)) - ceilf(ew * face);
1502
1503     uf = 0.5f * ewi * (uf + 1.f) - 0.5f;
1504     vf = 0.5f * ehi * (vf + 1.f) - 0.5f;
1505
1506     ui = floorf(uf);
1507     vi = floorf(vf);
1508
1509     *du = uf - ui;
1510     *dv = vf - vi;
1511
1512     for (int i = -1; i < 3; i++) {
1513         for (int j = -1; j < 3; j++) {
1514             int new_ui = ui + j;
1515             int new_vi = vi + i;
1516             int u_shift;
1517             int new_ewi;
1518
1519             if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1520                 face = s->in_cubemap_face_order[direction];
1521
1522                 u_shift = ceilf(ew * face);
1523             } else {
1524                 uf = 2.f * new_ui / ewi - 1.f;
1525                 vf = 2.f * new_vi / ehi - 1.f;
1526
1527                 uf /= scalew;
1528                 vf /= scaleh;
1529
1530                 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1531
1532                 uf *= scalew;
1533                 vf *= scaleh;
1534
1535                 u_shift = ceilf(ew * face);
1536                 new_ewi = ceilf(ew * (face + 1)) - u_shift;
1537
1538                 new_ui = av_clip(lrintf(0.5f * new_ewi * (uf + 1.f)), 0, new_ewi - 1);
1539                 new_vi = av_clip(lrintf(0.5f *     ehi * (vf + 1.f)), 0,     ehi - 1);
1540             }
1541
1542             us[i + 1][j + 1] = u_shift + new_ui;
1543             vs[i + 1][j + 1] =           new_vi;
1544         }
1545     }
1546
1547     return 1;
1548 }
1549
1550 /**
1551  * Calculate 3D coordinates on sphere for corresponding frame position in equirectangular format.
1552  *
1553  * @param s filter private context
1554  * @param i horizontal position on frame [0, width)
1555  * @param j vertical position on frame [0, height)
1556  * @param width frame width
1557  * @param height frame height
1558  * @param vec coordinates on sphere
1559  */
1560 static int equirect_to_xyz(const V360Context *s,
1561                            int i, int j, int width, int height,
1562                            float *vec)
1563 {
1564     const float phi   = ((2.f * i + 0.5f) / width  - 1.f) * M_PI;
1565     const float theta = ((2.f * j + 0.5f) / height - 1.f) * M_PI_2;
1566
1567     const float sin_phi   = sinf(phi);
1568     const float cos_phi   = cosf(phi);
1569     const float sin_theta = sinf(theta);
1570     const float cos_theta = cosf(theta);
1571
1572     vec[0] =  cos_theta * sin_phi;
1573     vec[1] = -sin_theta;
1574     vec[2] = -cos_theta * cos_phi;
1575
1576     return 1;
1577 }
1578
1579 /**
1580  * Prepare data for processing stereographic output format.
1581  *
1582  * @param ctx filter context
1583  *
1584  * @return error code
1585  */
1586 static int prepare_stereographic_out(AVFilterContext *ctx)
1587 {
1588     V360Context *s = ctx->priv;
1589
1590     s->flat_range[0] = tanf(FFMIN(s->h_fov, 359.f) * M_PI / 720.f);
1591     s->flat_range[1] = tanf(FFMIN(s->v_fov, 359.f) * M_PI / 720.f);
1592
1593     return 0;
1594 }
1595
1596 /**
1597  * Calculate 3D coordinates on sphere for corresponding frame position in stereographic format.
1598  *
1599  * @param s filter private context
1600  * @param i horizontal position on frame [0, width)
1601  * @param j vertical position on frame [0, height)
1602  * @param width frame width
1603  * @param height frame height
1604  * @param vec coordinates on sphere
1605  */
1606 static int stereographic_to_xyz(const V360Context *s,
1607                                 int i, int j, int width, int height,
1608                                 float *vec)
1609 {
1610     const float x = ((2.f * i) / width  - 1.f) * s->flat_range[0];
1611     const float y = ((2.f * j) / height - 1.f) * s->flat_range[1];
1612     const float xy = x * x + y * y;
1613
1614     vec[0] = 2.f * x / (1.f + xy);
1615     vec[1] = (-1.f + xy) / (1.f + xy);
1616     vec[2] = 2.f * y / (1.f + xy);
1617
1618     normalize_vector(vec);
1619
1620     return 1;
1621 }
1622
1623 /**
1624  * Prepare data for processing stereographic input format.
1625  *
1626  * @param ctx filter context
1627  *
1628  * @return error code
1629  */
1630 static int prepare_stereographic_in(AVFilterContext *ctx)
1631 {
1632     V360Context *s = ctx->priv;
1633
1634     s->iflat_range[0] = tanf(FFMIN(s->ih_fov, 359.f) * M_PI / 720.f);
1635     s->iflat_range[1] = tanf(FFMIN(s->iv_fov, 359.f) * M_PI / 720.f);
1636
1637     return 0;
1638 }
1639
1640 /**
1641  * Calculate frame position in stereographic format for corresponding 3D coordinates on sphere.
1642  *
1643  * @param s filter private context
1644  * @param vec coordinates on sphere
1645  * @param width frame width
1646  * @param height frame height
1647  * @param us horizontal coordinates for interpolation window
1648  * @param vs vertical coordinates for interpolation window
1649  * @param du horizontal relative coordinate
1650  * @param dv vertical relative coordinate
1651  */
1652 static int xyz_to_stereographic(const V360Context *s,
1653                                 const float *vec, int width, int height,
1654                                 int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1655 {
1656     const float x = vec[0] / (1.f - vec[1]) / s->iflat_range[0] * s->input_mirror_modifier[0];
1657     const float y = vec[2] / (1.f - vec[1]) / s->iflat_range[1] * s->input_mirror_modifier[1];
1658     float uf, vf;
1659     int visible, ui, vi;
1660
1661     uf = (x + 1.f) * width  / 2.f;
1662     vf = (y + 1.f) * height / 2.f;
1663     ui = floorf(uf);
1664     vi = floorf(vf);
1665
1666     visible = isfinite(x) && isfinite(y) && vi >= 0 && vi < height && ui >= 0 && ui < width;
1667
1668     *du = visible ? uf - ui : 0.f;
1669     *dv = visible ? vf - vi : 0.f;
1670
1671     for (int i = -1; i < 3; i++) {
1672         for (int j = -1; j < 3; j++) {
1673             us[i + 1][j + 1] = visible ? av_clip(ui + j, 0, width  - 1) : 0;
1674             vs[i + 1][j + 1] = visible ? av_clip(vi + i, 0, height - 1) : 0;
1675         }
1676     }
1677
1678     return visible;
1679 }
1680
1681 /**
1682  * Calculate frame position in equirectangular format for corresponding 3D coordinates on sphere.
1683  *
1684  * @param s filter private context
1685  * @param vec coordinates on sphere
1686  * @param width frame width
1687  * @param height frame height
1688  * @param us horizontal coordinates for interpolation window
1689  * @param vs vertical coordinates for interpolation window
1690  * @param du horizontal relative coordinate
1691  * @param dv vertical relative coordinate
1692  */
1693 static int xyz_to_equirect(const V360Context *s,
1694                            const float *vec, int width, int height,
1695                            int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1696 {
1697     const float phi   = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0];
1698     const float theta = asinf(-vec[1]) * s->input_mirror_modifier[1];
1699     float uf, vf;
1700     int ui, vi;
1701
1702     uf = (phi   / M_PI   + 1.f) * width  / 2.f;
1703     vf = (theta / M_PI_2 + 1.f) * height / 2.f;
1704     ui = floorf(uf);
1705     vi = floorf(vf);
1706
1707     *du = uf - ui;
1708     *dv = vf - vi;
1709
1710     for (int i = -1; i < 3; i++) {
1711         for (int j = -1; j < 3; j++) {
1712             us[i + 1][j + 1] = mod(ui + j, width);
1713             vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1714         }
1715     }
1716
1717     return 1;
1718 }
1719
1720 /**
1721  * Prepare data for processing flat input format.
1722  *
1723  * @param ctx filter context
1724  *
1725  * @return error code
1726  */
1727 static int prepare_flat_in(AVFilterContext *ctx)
1728 {
1729     V360Context *s = ctx->priv;
1730
1731     s->iflat_range[0] = tanf(0.5f * s->ih_fov * M_PI / 180.f);
1732     s->iflat_range[1] = tanf(0.5f * s->iv_fov * M_PI / 180.f);
1733
1734     return 0;
1735 }
1736
1737 /**
1738  * Calculate frame position in flat format for corresponding 3D coordinates on sphere.
1739  *
1740  * @param s filter private context
1741  * @param vec coordinates on sphere
1742  * @param width frame width
1743  * @param height frame height
1744  * @param us horizontal coordinates for interpolation window
1745  * @param vs vertical coordinates for interpolation window
1746  * @param du horizontal relative coordinate
1747  * @param dv vertical relative coordinate
1748  */
1749 static int xyz_to_flat(const V360Context *s,
1750                        const float *vec, int width, int height,
1751                        int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1752 {
1753     const float theta = acosf(vec[2]);
1754     const float r = tanf(theta);
1755     const float rr = fabsf(r) < 1e+6f ? r : hypotf(width, height);
1756     const float zf = -vec[2];
1757     const float h = hypotf(vec[0], vec[1]);
1758     const float c = h <= 1e-6f ? 1.f : rr / h;
1759     float uf = -vec[0] * c / s->iflat_range[0] * s->input_mirror_modifier[0];
1760     float vf =  vec[1] * c / s->iflat_range[1] * s->input_mirror_modifier[1];
1761     int visible, ui, vi;
1762
1763     uf = zf >= 0.f ? (uf + 1.f) * width  / 2.f : 0.f;
1764     vf = zf >= 0.f ? (vf + 1.f) * height / 2.f : 0.f;
1765
1766     ui = floorf(uf);
1767     vi = floorf(vf);
1768
1769     visible = vi >= 0 && vi < height && ui >= 0 && ui < width && zf >= 0.f;
1770
1771     *du = uf - ui;
1772     *dv = vf - vi;
1773
1774     for (int i = -1; i < 3; i++) {
1775         for (int j = -1; j < 3; j++) {
1776             us[i + 1][j + 1] = visible ? av_clip(ui + j, 0, width  - 1) : 0;
1777             vs[i + 1][j + 1] = visible ? av_clip(vi + i, 0, height - 1) : 0;
1778         }
1779     }
1780
1781     return visible;
1782 }
1783
1784 /**
1785  * Calculate frame position in mercator format for corresponding 3D coordinates on sphere.
1786  *
1787  * @param s filter private context
1788  * @param vec coordinates on sphere
1789  * @param width frame width
1790  * @param height frame height
1791  * @param us horizontal coordinates for interpolation window
1792  * @param vs vertical coordinates for interpolation window
1793  * @param du horizontal relative coordinate
1794  * @param dv vertical relative coordinate
1795  */
1796 static int xyz_to_mercator(const V360Context *s,
1797                            const float *vec, int width, int height,
1798                            int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1799 {
1800     const float phi   = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0];
1801     const float theta = -vec[1] * s->input_mirror_modifier[1];
1802     float uf, vf;
1803     int ui, vi;
1804
1805     uf = (phi / M_PI + 1.f) * width / 2.f;
1806     vf = (av_clipf(logf((1.f + theta) / (1.f - theta)) / (2.f * M_PI), -1.f, 1.f) + 1.f) * height / 2.f;
1807     ui = floorf(uf);
1808     vi = floorf(vf);
1809
1810     *du = uf - ui;
1811     *dv = vf - vi;
1812
1813     for (int i = -1; i < 3; i++) {
1814         for (int j = -1; j < 3; j++) {
1815             us[i + 1][j + 1] = av_clip(ui + j, 0, width  - 1);
1816             vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1817         }
1818     }
1819
1820     return 1;
1821 }
1822
1823 /**
1824  * Calculate 3D coordinates on sphere for corresponding frame position in mercator format.
1825  *
1826  * @param s filter private context
1827  * @param i horizontal position on frame [0, width)
1828  * @param j vertical position on frame [0, height)
1829  * @param width frame width
1830  * @param height frame height
1831  * @param vec coordinates on sphere
1832  */
1833 static int mercator_to_xyz(const V360Context *s,
1834                            int i, int j, int width, int height,
1835                            float *vec)
1836 {
1837     const float phi = ((2.f * i) / width - 1.f) * M_PI + M_PI_2;
1838     const float y   = ((2.f * j) / height - 1.f) * M_PI;
1839     const float div = expf(2.f * y) + 1.f;
1840
1841     const float sin_phi   = sinf(phi);
1842     const float cos_phi   = cosf(phi);
1843     const float sin_theta = -2.f * expf(y) / div;
1844     const float cos_theta = -(expf(2.f * y) - 1.f) / div;
1845
1846     vec[0] = sin_theta * cos_phi;
1847     vec[1] = cos_theta;
1848     vec[2] = sin_theta * sin_phi;
1849
1850     return 1;
1851 }
1852
1853 /**
1854  * Calculate frame position in ball format for corresponding 3D coordinates on sphere.
1855  *
1856  * @param s filter private context
1857  * @param vec coordinates on sphere
1858  * @param width frame width
1859  * @param height frame height
1860  * @param us horizontal coordinates for interpolation window
1861  * @param vs vertical coordinates for interpolation window
1862  * @param du horizontal relative coordinate
1863  * @param dv vertical relative coordinate
1864  */
1865 static int xyz_to_ball(const V360Context *s,
1866                        const float *vec, int width, int height,
1867                        int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1868 {
1869     const float l = hypotf(vec[0], vec[1]);
1870     const float r = sqrtf(1.f + vec[2]) / M_SQRT2;
1871     float uf, vf;
1872     int ui, vi;
1873
1874     uf = (1.f + r * vec[0] * s->input_mirror_modifier[0] / (l > 0.f ? l : 1.f)) * width  * 0.5f;
1875     vf = (1.f - r * vec[1] * s->input_mirror_modifier[1] / (l > 0.f ? l : 1.f)) * height * 0.5f;
1876
1877     ui = floorf(uf);
1878     vi = floorf(vf);
1879
1880     *du = uf - ui;
1881     *dv = vf - vi;
1882
1883     for (int i = -1; i < 3; i++) {
1884         for (int j = -1; j < 3; j++) {
1885             us[i + 1][j + 1] = av_clip(ui + j, 0, width  - 1);
1886             vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
1887         }
1888     }
1889
1890     return 1;
1891 }
1892
1893 /**
1894  * Calculate 3D coordinates on sphere for corresponding frame position in ball format.
1895  *
1896  * @param s filter private context
1897  * @param i horizontal position on frame [0, width)
1898  * @param j vertical position on frame [0, height)
1899  * @param width frame width
1900  * @param height frame height
1901  * @param vec coordinates on sphere
1902  */
1903 static int ball_to_xyz(const V360Context *s,
1904                        int i, int j, int width, int height,
1905                        float *vec)
1906 {
1907     const float x = (2.f * i) / width  - 1.f;
1908     const float y = (2.f * j) / height - 1.f;
1909     const float l = hypotf(x, y);
1910
1911     if (l <= 1.f) {
1912         const float z = 2.f * l * sqrtf(1.f - l * l);
1913
1914         vec[0] =  z * x / (l > 0.f ? l : 1.f);
1915         vec[1] = -z * y / (l > 0.f ? l : 1.f);
1916         vec[2] = -1.f + 2.f * l * l;
1917     } else {
1918         vec[0] =  0.f;
1919         vec[1] = -1.f;
1920         vec[2] =  0.f;
1921         return 0;
1922     }
1923
1924     return 1;
1925 }
1926
1927 /**
1928  * Calculate 3D coordinates on sphere for corresponding frame position in hammer format.
1929  *
1930  * @param s filter private context
1931  * @param i horizontal position on frame [0, width)
1932  * @param j vertical position on frame [0, height)
1933  * @param width frame width
1934  * @param height frame height
1935  * @param vec coordinates on sphere
1936  */
1937 static int hammer_to_xyz(const V360Context *s,
1938                          int i, int j, int width, int height,
1939                          float *vec)
1940 {
1941     const float x = ((2.f * i) / width  - 1.f);
1942     const float y = ((2.f * j) / height - 1.f);
1943
1944     const float xx = x * x;
1945     const float yy = y * y;
1946
1947     const float z = sqrtf(1.f - xx * 0.5f - yy * 0.5f);
1948
1949     const float a = M_SQRT2 * x * z;
1950     const float b = 2.f * z * z - 1.f;
1951
1952     const float aa = a * a;
1953     const float bb = b * b;
1954
1955     const float w = sqrtf(1.f - 2.f * yy * z * z);
1956
1957     vec[0] =  w * 2.f * a * b / (aa + bb);
1958     vec[1] = -M_SQRT2 * y * z;
1959     vec[2] = -w * (bb  - aa) / (aa + bb);
1960
1961     normalize_vector(vec);
1962
1963     return 1;
1964 }
1965
1966 /**
1967  * Calculate frame position in hammer format for corresponding 3D coordinates on sphere.
1968  *
1969  * @param s filter private context
1970  * @param vec coordinates on sphere
1971  * @param width frame width
1972  * @param height frame height
1973  * @param us horizontal coordinates for interpolation window
1974  * @param vs vertical coordinates for interpolation window
1975  * @param du horizontal relative coordinate
1976  * @param dv vertical relative coordinate
1977  */
1978 static int xyz_to_hammer(const V360Context *s,
1979                          const float *vec, int width, int height,
1980                          int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1981 {
1982     const float theta = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0];
1983
1984     const float z = sqrtf(1.f + sqrtf(1.f - vec[1] * vec[1]) * cosf(theta * 0.5f));
1985     const float x = sqrtf(1.f - vec[1] * vec[1]) * sinf(theta * 0.5f) / z;
1986     const float y = -vec[1] / z * s->input_mirror_modifier[1];
1987     float uf, vf;
1988     int ui, vi;
1989
1990     uf = (x + 1.f) * width  / 2.f;
1991     vf = (y + 1.f) * height / 2.f;
1992     ui = floorf(uf);
1993     vi = floorf(vf);
1994
1995     *du = uf - ui;
1996     *dv = vf - vi;
1997
1998     for (int i = -1; i < 3; i++) {
1999         for (int j = -1; j < 3; j++) {
2000             us[i + 1][j + 1] = av_clip(ui + j, 0, width  - 1);
2001             vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
2002         }
2003     }
2004
2005     return 1;
2006 }
2007
2008 /**
2009  * Calculate 3D coordinates on sphere for corresponding frame position in sinusoidal format.
2010  *
2011  * @param s filter private context
2012  * @param i horizontal position on frame [0, width)
2013  * @param j vertical position on frame [0, height)
2014  * @param width frame width
2015  * @param height frame height
2016  * @param vec coordinates on sphere
2017  */
2018 static int sinusoidal_to_xyz(const V360Context *s,
2019                              int i, int j, int width, int height,
2020                              float *vec)
2021 {
2022     const float theta = ((2.f * j) / height - 1.f) * M_PI_2;
2023     const float phi   = ((2.f * i) / width  - 1.f) * M_PI / cosf(theta);
2024
2025     const float sin_phi   = sinf(phi);
2026     const float cos_phi   = cosf(phi);
2027     const float sin_theta = sinf(theta);
2028     const float cos_theta = cosf(theta);
2029
2030     vec[0] =  cos_theta * sin_phi;
2031     vec[1] = -sin_theta;
2032     vec[2] = -cos_theta * cos_phi;
2033
2034     normalize_vector(vec);
2035
2036     return 1;
2037 }
2038
2039 /**
2040  * Calculate frame position in sinusoidal format for corresponding 3D coordinates on sphere.
2041  *
2042  * @param s filter private context
2043  * @param vec coordinates on sphere
2044  * @param width frame width
2045  * @param height frame height
2046  * @param us horizontal coordinates for interpolation window
2047  * @param vs vertical coordinates for interpolation window
2048  * @param du horizontal relative coordinate
2049  * @param dv vertical relative coordinate
2050  */
2051 static int xyz_to_sinusoidal(const V360Context *s,
2052                              const float *vec, int width, int height,
2053                              int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2054 {
2055     const float theta = asinf(-vec[1]) * s->input_mirror_modifier[1];
2056     const float phi   = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0] * cosf(theta);
2057     float uf, vf;
2058     int ui, vi;
2059
2060     uf = (phi   / M_PI   + 1.f) * width  / 2.f;
2061     vf = (theta / M_PI_2 + 1.f) * height / 2.f;
2062     ui = floorf(uf);
2063     vi = floorf(vf);
2064
2065     *du = uf - ui;
2066     *dv = vf - vi;
2067
2068     for (int i = -1; i < 3; i++) {
2069         for (int j = -1; j < 3; j++) {
2070             us[i + 1][j + 1] = av_clip(ui + j, 0, width  - 1);
2071             vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
2072         }
2073     }
2074
2075     return 1;
2076 }
2077
2078 /**
2079  * Prepare data for processing equi-angular cubemap input format.
2080  *
2081  * @param ctx filter context
2082  *
2083  * @return error code
2084  */
2085 static int prepare_eac_in(AVFilterContext *ctx)
2086 {
2087     V360Context *s = ctx->priv;
2088
2089     if (s->ih_flip && s->iv_flip) {
2090         s->in_cubemap_face_order[RIGHT] = BOTTOM_LEFT;
2091         s->in_cubemap_face_order[LEFT]  = BOTTOM_RIGHT;
2092         s->in_cubemap_face_order[UP]    = TOP_LEFT;
2093         s->in_cubemap_face_order[DOWN]  = TOP_RIGHT;
2094         s->in_cubemap_face_order[FRONT] = BOTTOM_MIDDLE;
2095         s->in_cubemap_face_order[BACK]  = TOP_MIDDLE;
2096     } else if (s->ih_flip) {
2097         s->in_cubemap_face_order[RIGHT] = TOP_LEFT;
2098         s->in_cubemap_face_order[LEFT]  = TOP_RIGHT;
2099         s->in_cubemap_face_order[UP]    = BOTTOM_LEFT;
2100         s->in_cubemap_face_order[DOWN]  = BOTTOM_RIGHT;
2101         s->in_cubemap_face_order[FRONT] = TOP_MIDDLE;
2102         s->in_cubemap_face_order[BACK]  = BOTTOM_MIDDLE;
2103     } else if (s->iv_flip) {
2104         s->in_cubemap_face_order[RIGHT] = BOTTOM_RIGHT;
2105         s->in_cubemap_face_order[LEFT]  = BOTTOM_LEFT;
2106         s->in_cubemap_face_order[UP]    = TOP_RIGHT;
2107         s->in_cubemap_face_order[DOWN]  = TOP_LEFT;
2108         s->in_cubemap_face_order[FRONT] = BOTTOM_MIDDLE;
2109         s->in_cubemap_face_order[BACK]  = TOP_MIDDLE;
2110     } else {
2111         s->in_cubemap_face_order[RIGHT] = TOP_RIGHT;
2112         s->in_cubemap_face_order[LEFT]  = TOP_LEFT;
2113         s->in_cubemap_face_order[UP]    = BOTTOM_RIGHT;
2114         s->in_cubemap_face_order[DOWN]  = BOTTOM_LEFT;
2115         s->in_cubemap_face_order[FRONT] = TOP_MIDDLE;
2116         s->in_cubemap_face_order[BACK]  = BOTTOM_MIDDLE;
2117     }
2118
2119     if (s->iv_flip) {
2120         s->in_cubemap_face_rotation[TOP_LEFT]      = ROT_270;
2121         s->in_cubemap_face_rotation[TOP_MIDDLE]    = ROT_90;
2122         s->in_cubemap_face_rotation[TOP_RIGHT]     = ROT_270;
2123         s->in_cubemap_face_rotation[BOTTOM_LEFT]   = ROT_0;
2124         s->in_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_0;
2125         s->in_cubemap_face_rotation[BOTTOM_RIGHT]  = ROT_0;
2126     } else {
2127         s->in_cubemap_face_rotation[TOP_LEFT]      = ROT_0;
2128         s->in_cubemap_face_rotation[TOP_MIDDLE]    = ROT_0;
2129         s->in_cubemap_face_rotation[TOP_RIGHT]     = ROT_0;
2130         s->in_cubemap_face_rotation[BOTTOM_LEFT]   = ROT_270;
2131         s->in_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_90;
2132         s->in_cubemap_face_rotation[BOTTOM_RIGHT]  = ROT_270;
2133     }
2134
2135     return 0;
2136 }
2137
2138 /**
2139  * Prepare data for processing equi-angular cubemap output format.
2140  *
2141  * @param ctx filter context
2142  *
2143  * @return error code
2144  */
2145 static int prepare_eac_out(AVFilterContext *ctx)
2146 {
2147     V360Context *s = ctx->priv;
2148
2149     s->out_cubemap_direction_order[TOP_LEFT]      = LEFT;
2150     s->out_cubemap_direction_order[TOP_MIDDLE]    = FRONT;
2151     s->out_cubemap_direction_order[TOP_RIGHT]     = RIGHT;
2152     s->out_cubemap_direction_order[BOTTOM_LEFT]   = DOWN;
2153     s->out_cubemap_direction_order[BOTTOM_MIDDLE] = BACK;
2154     s->out_cubemap_direction_order[BOTTOM_RIGHT]  = UP;
2155
2156     s->out_cubemap_face_rotation[TOP_LEFT]      = ROT_0;
2157     s->out_cubemap_face_rotation[TOP_MIDDLE]    = ROT_0;
2158     s->out_cubemap_face_rotation[TOP_RIGHT]     = ROT_0;
2159     s->out_cubemap_face_rotation[BOTTOM_LEFT]   = ROT_270;
2160     s->out_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_90;
2161     s->out_cubemap_face_rotation[BOTTOM_RIGHT]  = ROT_270;
2162
2163     return 0;
2164 }
2165
2166 /**
2167  * Calculate 3D coordinates on sphere for corresponding frame position in equi-angular cubemap format.
2168  *
2169  * @param s filter private context
2170  * @param i horizontal position on frame [0, width)
2171  * @param j vertical position on frame [0, height)
2172  * @param width frame width
2173  * @param height frame height
2174  * @param vec coordinates on sphere
2175  */
2176 static int eac_to_xyz(const V360Context *s,
2177                       int i, int j, int width, int height,
2178                       float *vec)
2179 {
2180     const float pixel_pad = 2;
2181     const float u_pad = pixel_pad / width;
2182     const float v_pad = pixel_pad / height;
2183
2184     int u_face, v_face, face;
2185
2186     float l_x, l_y, l_z;
2187
2188     float uf = (i + 0.5f) / width;
2189     float vf = (j + 0.5f) / height;
2190
2191     // EAC has 2-pixel padding on faces except between faces on the same row
2192     // Padding pixels seems not to be stretched with tangent as regular pixels
2193     // Formulas below approximate original padding as close as I could get experimentally
2194
2195     // Horizontal padding
2196     uf = 3.f * (uf - u_pad) / (1.f - 2.f * u_pad);
2197     if (uf < 0.f) {
2198         u_face = 0;
2199         uf -= 0.5f;
2200     } else if (uf >= 3.f) {
2201         u_face = 2;
2202         uf -= 2.5f;
2203     } else {
2204         u_face = floorf(uf);
2205         uf = fmodf(uf, 1.f) - 0.5f;
2206     }
2207
2208     // Vertical padding
2209     v_face = floorf(vf * 2.f);
2210     vf = (vf - v_pad - 0.5f * v_face) / (0.5f - 2.f * v_pad) - 0.5f;
2211
2212     if (uf >= -0.5f && uf < 0.5f) {
2213         uf = tanf(M_PI_2 * uf);
2214     } else {
2215         uf = 2.f * uf;
2216     }
2217     if (vf >= -0.5f && vf < 0.5f) {
2218         vf = tanf(M_PI_2 * vf);
2219     } else {
2220         vf = 2.f * vf;
2221     }
2222
2223     face = u_face + 3 * v_face;
2224
2225     switch (face) {
2226     case TOP_LEFT:
2227         l_x = -1.f;
2228         l_y = -vf;
2229         l_z = -uf;
2230         break;
2231     case TOP_MIDDLE:
2232         l_x =  uf;
2233         l_y = -vf;
2234         l_z = -1.f;
2235         break;
2236     case TOP_RIGHT:
2237         l_x =  1.f;
2238         l_y = -vf;
2239         l_z =  uf;
2240         break;
2241     case BOTTOM_LEFT:
2242         l_x = -vf;
2243         l_y = -1.f;
2244         l_z =  uf;
2245         break;
2246     case BOTTOM_MIDDLE:
2247         l_x = -vf;
2248         l_y =  uf;
2249         l_z =  1.f;
2250         break;
2251     case BOTTOM_RIGHT:
2252         l_x = -vf;
2253         l_y =  1.f;
2254         l_z = -uf;
2255         break;
2256     default:
2257         av_assert0(0);
2258     }
2259
2260     vec[0] = l_x;
2261     vec[1] = l_y;
2262     vec[2] = l_z;
2263
2264     normalize_vector(vec);
2265
2266     return 1;
2267 }
2268
2269 /**
2270  * Calculate frame position in equi-angular cubemap format for corresponding 3D coordinates on sphere.
2271  *
2272  * @param s filter private context
2273  * @param vec coordinates on sphere
2274  * @param width frame width
2275  * @param height frame height
2276  * @param us horizontal coordinates for interpolation window
2277  * @param vs vertical coordinates for interpolation window
2278  * @param du horizontal relative coordinate
2279  * @param dv vertical relative coordinate
2280  */
2281 static int xyz_to_eac(const V360Context *s,
2282                       const float *vec, int width, int height,
2283                       int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2284 {
2285     const float pixel_pad = 2;
2286     const float u_pad = pixel_pad / width;
2287     const float v_pad = pixel_pad / height;
2288
2289     float uf, vf;
2290     int ui, vi;
2291     int direction, face;
2292     int u_face, v_face;
2293
2294     xyz_to_cube(s, vec, &uf, &vf, &direction);
2295
2296     face = s->in_cubemap_face_order[direction];
2297     u_face = face % 3;
2298     v_face = face / 3;
2299
2300     uf = M_2_PI * atanf(uf) + 0.5f;
2301     vf = M_2_PI * atanf(vf) + 0.5f;
2302
2303     // These formulas are inversed from eac_to_xyz ones
2304     uf = (uf + u_face) * (1.f - 2.f * u_pad) / 3.f + u_pad;
2305     vf = vf * (0.5f - 2.f * v_pad) + v_pad + 0.5f * v_face;
2306
2307     uf *= width;
2308     vf *= height;
2309
2310     uf -= 0.5f;
2311     vf -= 0.5f;
2312
2313     ui = floorf(uf);
2314     vi = floorf(vf);
2315
2316     *du = uf - ui;
2317     *dv = vf - vi;
2318
2319     for (int i = -1; i < 3; i++) {
2320         for (int j = -1; j < 3; j++) {
2321             us[i + 1][j + 1] = av_clip(ui + j, 0, width  - 1);
2322             vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
2323         }
2324     }
2325
2326     return 1;
2327 }
2328
2329 /**
2330  * Prepare data for processing flat output format.
2331  *
2332  * @param ctx filter context
2333  *
2334  * @return error code
2335  */
2336 static int prepare_flat_out(AVFilterContext *ctx)
2337 {
2338     V360Context *s = ctx->priv;
2339
2340     s->flat_range[0] = tanf(0.5f * s->h_fov * M_PI / 180.f);
2341     s->flat_range[1] = tanf(0.5f * s->v_fov * M_PI / 180.f);
2342
2343     return 0;
2344 }
2345
2346 /**
2347  * Calculate 3D coordinates on sphere for corresponding frame position in flat format.
2348  *
2349  * @param s filter private context
2350  * @param i horizontal position on frame [0, width)
2351  * @param j vertical position on frame [0, height)
2352  * @param width frame width
2353  * @param height frame height
2354  * @param vec coordinates on sphere
2355  */
2356 static int flat_to_xyz(const V360Context *s,
2357                        int i, int j, int width, int height,
2358                        float *vec)
2359 {
2360     const float l_x =  s->flat_range[0] * (2.f * i / width  - 1.f);
2361     const float l_y = -s->flat_range[1] * (2.f * j / height - 1.f);
2362
2363     vec[0] =  l_x;
2364     vec[1] =  l_y;
2365     vec[2] = -1.f;
2366
2367     normalize_vector(vec);
2368
2369     return 1;
2370 }
2371
2372 /**
2373  * Prepare data for processing fisheye output format.
2374  *
2375  * @param ctx filter context
2376  *
2377  * @return error code
2378  */
2379 static int prepare_fisheye_out(AVFilterContext *ctx)
2380 {
2381     V360Context *s = ctx->priv;
2382
2383     s->flat_range[0] = s->h_fov / 180.f;
2384     s->flat_range[1] = s->v_fov / 180.f;
2385
2386     return 0;
2387 }
2388
2389 /**
2390  * Calculate 3D coordinates on sphere for corresponding frame position in fisheye format.
2391  *
2392  * @param s filter private context
2393  * @param i horizontal position on frame [0, width)
2394  * @param j vertical position on frame [0, height)
2395  * @param width frame width
2396  * @param height frame height
2397  * @param vec coordinates on sphere
2398  */
2399 static int fisheye_to_xyz(const V360Context *s,
2400                           int i, int j, int width, int height,
2401                           float *vec)
2402 {
2403     const float uf = s->flat_range[0] * ((2.f * i) / width  - 1.f);
2404     const float vf = s->flat_range[1] * ((2.f * j + 1.f) / height - 1.f);
2405
2406     const float phi   = -atan2f(vf, uf);
2407     const float theta = -M_PI_2 * (1.f - hypotf(uf, vf));
2408
2409     vec[0] = cosf(theta) * cosf(phi);
2410     vec[1] = cosf(theta) * sinf(phi);
2411     vec[2] = sinf(theta);
2412
2413     normalize_vector(vec);
2414
2415     return 1;
2416 }
2417
2418 /**
2419  * Prepare data for processing fisheye input format.
2420  *
2421  * @param ctx filter context
2422  *
2423  * @return error code
2424  */
2425 static int prepare_fisheye_in(AVFilterContext *ctx)
2426 {
2427     V360Context *s = ctx->priv;
2428
2429     s->iflat_range[0] = s->ih_fov / 180.f;
2430     s->iflat_range[1] = s->iv_fov / 180.f;
2431
2432     return 0;
2433 }
2434
2435 /**
2436  * Calculate frame position in fisheye format for corresponding 3D coordinates on sphere.
2437  *
2438  * @param s filter private context
2439  * @param vec coordinates on sphere
2440  * @param width frame width
2441  * @param height frame height
2442  * @param us horizontal coordinates for interpolation window
2443  * @param vs vertical coordinates for interpolation window
2444  * @param du horizontal relative coordinate
2445  * @param dv vertical relative coordinate
2446  */
2447 static int xyz_to_fisheye(const V360Context *s,
2448                           const float *vec, int width, int height,
2449                           int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2450 {
2451     const float phi   = -atan2f(hypotf(vec[0], vec[1]), -vec[2]) / M_PI;
2452     const float theta = -atan2f(vec[0], vec[1]);
2453
2454     float uf = sinf(theta) * phi * s->input_mirror_modifier[0] / s->iflat_range[0];
2455     float vf = cosf(theta) * phi * s->input_mirror_modifier[1] / s->iflat_range[1];
2456
2457     const int visible = hypotf(uf, vf) <= 0.5f;
2458     int ui, vi;
2459
2460     uf = (uf + 0.5f) * width;
2461     vf = (vf + 0.5f) * height;
2462
2463     ui = floorf(uf);
2464     vi = floorf(vf);
2465
2466     *du = visible ? uf - ui : 0.f;
2467     *dv = visible ? vf - vi : 0.f;
2468
2469     for (int i = -1; i < 3; i++) {
2470         for (int j = -1; j < 3; j++) {
2471             us[i + 1][j + 1] = visible ? av_clip(ui + j, 0, width  - 1) : 0;
2472             vs[i + 1][j + 1] = visible ? av_clip(vi + i, 0, height - 1) : 0;
2473         }
2474     }
2475
2476     return visible;
2477 }
2478
2479 /**
2480  * Calculate 3D coordinates on sphere for corresponding frame position in pannini format.
2481  *
2482  * @param s filter private context
2483  * @param i horizontal position on frame [0, width)
2484  * @param j vertical position on frame [0, height)
2485  * @param width frame width
2486  * @param height frame height
2487  * @param vec coordinates on sphere
2488  */
2489 static int pannini_to_xyz(const V360Context *s,
2490                           int i, int j, int width, int height,
2491                           float *vec)
2492 {
2493     const float uf = ((2.f * i) / width  - 1.f);
2494     const float vf = ((2.f * j) / height - 1.f);
2495
2496     const float d = s->h_fov;
2497     const float k = uf * uf / ((d + 1.f) * (d + 1.f));
2498     const float dscr = k * k * d * d - (k + 1.f) * (k * d * d - 1.f);
2499     const float clon = (-k * d + sqrtf(dscr)) / (k + 1.f);
2500     const float S = (d + 1.f) / (d + clon);
2501     const float lon = -(M_PI + atan2f(uf, S * clon));
2502     const float lat = -atan2f(vf, S);
2503
2504     vec[0] = sinf(lon) * cosf(lat);
2505     vec[1] = sinf(lat);
2506     vec[2] = cosf(lon) * cosf(lat);
2507
2508     normalize_vector(vec);
2509
2510     return 1;
2511 }
2512
2513 /**
2514  * Prepare data for processing cylindrical output format.
2515  *
2516  * @param ctx filter context
2517  *
2518  * @return error code
2519  */
2520 static int prepare_cylindrical_out(AVFilterContext *ctx)
2521 {
2522     V360Context *s = ctx->priv;
2523
2524     s->flat_range[0] = M_PI * s->h_fov / 360.f;
2525     s->flat_range[1] = tanf(0.5f * s->v_fov * M_PI / 180.f);
2526
2527     return 0;
2528 }
2529
2530 /**
2531  * Calculate 3D coordinates on sphere for corresponding frame position in cylindrical format.
2532  *
2533  * @param s filter private context
2534  * @param i horizontal position on frame [0, width)
2535  * @param j vertical position on frame [0, height)
2536  * @param width frame width
2537  * @param height frame height
2538  * @param vec coordinates on sphere
2539  */
2540 static int cylindrical_to_xyz(const V360Context *s,
2541                               int i, int j, int width, int height,
2542                               float *vec)
2543 {
2544     const float uf = s->flat_range[0] * ((2.f * i) / width  - 1.f);
2545     const float vf = s->flat_range[1] * ((2.f * j) / height - 1.f);
2546
2547     const float phi   = uf;
2548     const float theta = atanf(vf);
2549
2550     const float sin_phi   = sinf(phi);
2551     const float cos_phi   = cosf(phi);
2552     const float sin_theta = sinf(theta);
2553     const float cos_theta = cosf(theta);
2554
2555     vec[0] =  cos_theta * sin_phi;
2556     vec[1] = -sin_theta;
2557     vec[2] = -cos_theta * cos_phi;
2558
2559     normalize_vector(vec);
2560
2561     return 1;
2562 }
2563
2564 /**
2565  * Prepare data for processing cylindrical input format.
2566  *
2567  * @param ctx filter context
2568  *
2569  * @return error code
2570  */
2571 static int prepare_cylindrical_in(AVFilterContext *ctx)
2572 {
2573     V360Context *s = ctx->priv;
2574
2575     s->iflat_range[0] = M_PI * s->ih_fov / 360.f;
2576     s->iflat_range[1] = tanf(0.5f * s->iv_fov * M_PI / 180.f);
2577
2578     return 0;
2579 }
2580
2581 /**
2582  * Calculate frame position in cylindrical format for corresponding 3D coordinates on sphere.
2583  *
2584  * @param s filter private context
2585  * @param vec coordinates on sphere
2586  * @param width frame width
2587  * @param height frame height
2588  * @param us horizontal coordinates for interpolation window
2589  * @param vs vertical coordinates for interpolation window
2590  * @param du horizontal relative coordinate
2591  * @param dv vertical relative coordinate
2592  */
2593 static int xyz_to_cylindrical(const V360Context *s,
2594                               const float *vec, int width, int height,
2595                               int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2596 {
2597     const float phi   = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0] / s->iflat_range[0];
2598     const float theta = atan2f(-vec[1], hypotf(vec[0], vec[2])) * s->input_mirror_modifier[1] / s->iflat_range[1];
2599     int visible, ui, vi;
2600     float uf, vf;
2601
2602     uf = (phi + 1.f) * (width - 1) / 2.f;
2603     vf = (tanf(theta) + 1.f) * height / 2.f;
2604     ui = floorf(uf);
2605     vi = floorf(vf);
2606
2607     visible = vi >= 0 && vi < height && ui >= 0 && ui < width &&
2608               theta <=  M_PI * s->iv_fov / 180.f &&
2609               theta >= -M_PI * s->iv_fov / 180.f;
2610
2611     *du = uf - ui;
2612     *dv = vf - vi;
2613
2614     for (int i = -1; i < 3; i++) {
2615         for (int j = -1; j < 3; j++) {
2616             us[i + 1][j + 1] = visible ? av_clip(ui + j, 0, width  - 1) : 0;
2617             vs[i + 1][j + 1] = visible ? av_clip(vi + i, 0, height - 1) : 0;
2618         }
2619     }
2620
2621     return visible;
2622 }
2623
2624 /**
2625  * Calculate 3D coordinates on sphere for corresponding frame position in perspective format.
2626  *
2627  * @param s filter private context
2628  * @param i horizontal position on frame [0, width)
2629  * @param j vertical position on frame [0, height)
2630  * @param width frame width
2631  * @param height frame height
2632  * @param vec coordinates on sphere
2633  */
2634 static int perspective_to_xyz(const V360Context *s,
2635                               int i, int j, int width, int height,
2636                               float *vec)
2637 {
2638     const float uf = ((2.f * i) / width  - 1.f);
2639     const float vf = ((2.f * j) / height - 1.f);
2640     const float rh = hypotf(uf, vf);
2641     const float sinzz = 1.f - rh * rh;
2642     const float h = 1.f + s->v_fov;
2643     const float sinz = (h - sqrtf(sinzz)) / (h / rh + rh / h);
2644     const float sinz2 = sinz * sinz;
2645
2646     if (sinz2 <= 1.f) {
2647         const float cosz = sqrtf(1.f - sinz2);
2648
2649         const float theta = asinf(cosz);
2650         const float phi   = atan2f(uf, vf);
2651
2652         const float sin_phi   = sinf(phi);
2653         const float cos_phi   = cosf(phi);
2654         const float sin_theta = sinf(theta);
2655         const float cos_theta = cosf(theta);
2656
2657         vec[0] =  cos_theta * sin_phi;
2658         vec[1] =  sin_theta;
2659         vec[2] = -cos_theta * cos_phi;
2660     } else {
2661         vec[0] =  0.f;
2662         vec[1] = -1.f;
2663         vec[2] =  0.f;
2664         return 0;
2665     }
2666
2667     normalize_vector(vec);
2668     return 1;
2669 }
2670
2671 /**
2672  * Calculate 3D coordinates on sphere for corresponding frame position in tetrahedron format.
2673  *
2674  * @param s filter private context
2675  * @param i horizontal position on frame [0, width)
2676  * @param j vertical position on frame [0, height)
2677  * @param width frame width
2678  * @param height frame height
2679  * @param vec coordinates on sphere
2680  */
2681 static int tetrahedron_to_xyz(const V360Context *s,
2682                               int i, int j, int width, int height,
2683                               float *vec)
2684 {
2685     const float uf = (float)i / width;
2686     const float vf = (float)j / height;
2687
2688     vec[0] = uf < 0.5f ? uf * 4.f - 1.f : 3.f - uf * 4.f;
2689     vec[1] = 1.f - vf * 2.f;
2690     vec[2] = 2.f * fabsf(1.f - fabsf(1.f - uf * 2.f + vf)) - 1.f;
2691
2692     normalize_vector(vec);
2693
2694     return 1;
2695 }
2696
2697 /**
2698  * Calculate frame position in tetrahedron format for corresponding 3D coordinates on sphere.
2699  *
2700  * @param s filter private context
2701  * @param vec coordinates on sphere
2702  * @param width frame width
2703  * @param height frame height
2704  * @param us horizontal coordinates for interpolation window
2705  * @param vs vertical coordinates for interpolation window
2706  * @param du horizontal relative coordinate
2707  * @param dv vertical relative coordinate
2708  */
2709 static int xyz_to_tetrahedron(const V360Context *s,
2710                               const float *vec, int width, int height,
2711                               int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2712 {
2713     float d = 0.5f * (vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
2714
2715     const float d0 = (vec[0] * 0.5f + vec[1] * 0.5f + vec[2] *-0.5f) / d;
2716     const float d1 = (vec[0] *-0.5f + vec[1] *-0.5f + vec[2] *-0.5f) / d;
2717     const float d2 = (vec[0] * 0.5f + vec[1] *-0.5f + vec[2] * 0.5f) / d;
2718     const float d3 = (vec[0] *-0.5f + vec[1] * 0.5f + vec[2] * 0.5f) / d;
2719
2720     float uf, vf, x, y, z;
2721     int ui, vi;
2722
2723     d = FFMAX(d0, FFMAX3(d1, d2, d3));
2724
2725     x =  vec[0] / d;
2726     y =  vec[1] / d;
2727     z = -vec[2] / d;
2728
2729     vf = 0.5f - y * 0.5f * s->input_mirror_modifier[1];
2730
2731     if ((x + y >= 0.f &&  y + z >= 0.f && -z - x <= 0.f) ||
2732         (x + y <= 0.f && -y + z >= 0.f &&  z - x >= 0.f)) {
2733         uf = 0.25f * x * s->input_mirror_modifier[0] + 0.25f;
2734     }  else {
2735         uf = 0.75f - 0.25f * x * s->input_mirror_modifier[0];
2736     }
2737
2738     uf *= width;
2739     vf *= height;
2740
2741     ui = floorf(uf);
2742     vi = floorf(vf);
2743
2744     *du = uf - ui;
2745     *dv = vf - vi;
2746
2747     for (int i = -1; i < 3; i++) {
2748         for (int j = -1; j < 3; j++) {
2749             us[i + 1][j + 1] = mod(ui + j, width);
2750             vs[i + 1][j + 1] = av_clip(vi + i, 0, height - 1);
2751         }
2752     }
2753
2754     return 1;
2755 }
2756
2757 /**
2758  * Calculate 3D coordinates on sphere for corresponding frame position in dual fisheye format.
2759  *
2760  * @param s filter private context
2761  * @param i horizontal position on frame [0, width)
2762  * @param j vertical position on frame [0, height)
2763  * @param width frame width
2764  * @param height frame height
2765  * @param vec coordinates on sphere
2766  */
2767 static int dfisheye_to_xyz(const V360Context *s,
2768                            int i, int j, int width, int height,
2769                            float *vec)
2770 {
2771     const float scale = 1.f + s->out_pad;
2772
2773     const float ew = width / 2.f;
2774     const float eh = height;
2775
2776     const int ei = i >= ew ? i - ew : i;
2777     const float m = i >= ew ? -1.f : 1.f;
2778
2779     const float uf = ((2.f * ei) / ew - 1.f) * scale;
2780     const float vf = ((2.f * j + 1.f) / eh - 1.f) * scale;
2781
2782     const float h     = hypotf(uf, vf);
2783     const float lh    = h > 0.f ? h : 1.f;
2784     const float theta = m * M_PI_2 * (1.f - h);
2785
2786     const float sin_theta = sinf(theta);
2787     const float cos_theta = cosf(theta);
2788
2789     vec[0] = cos_theta * m * -uf / lh;
2790     vec[1] = cos_theta *     -vf / lh;
2791     vec[2] = sin_theta;
2792
2793     normalize_vector(vec);
2794
2795     return 1;
2796 }
2797
2798 /**
2799  * Calculate frame position in dual fisheye format for corresponding 3D coordinates on sphere.
2800  *
2801  * @param s filter private context
2802  * @param vec coordinates on sphere
2803  * @param width frame width
2804  * @param height frame height
2805  * @param us horizontal coordinates for interpolation window
2806  * @param vs vertical coordinates for interpolation window
2807  * @param du horizontal relative coordinate
2808  * @param dv vertical relative coordinate
2809  */
2810 static int xyz_to_dfisheye(const V360Context *s,
2811                            const float *vec, int width, int height,
2812                            int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2813 {
2814     const float scale = 1.f - s->in_pad;
2815
2816     const float ew = width / 2.f;
2817     const float eh = height;
2818
2819     const float h     = hypotf(vec[0], vec[1]);
2820     const float lh    = h > 0.f ? h : 1.f;
2821     const float theta = acosf(fabsf(vec[2])) / M_PI;
2822
2823     float uf = (theta * (-vec[0] / lh) * s->input_mirror_modifier[0] * scale + 0.5f) * ew;
2824     float vf = (theta * (-vec[1] / lh) * s->input_mirror_modifier[1] * scale + 0.5f) * eh;
2825
2826     int ui, vi;
2827     int u_shift;
2828
2829     if (vec[2] >= 0.f) {
2830         u_shift = 0;
2831     } else {
2832         u_shift = ceilf(ew);
2833         uf = ew - uf;
2834     }
2835
2836     ui = floorf(uf);
2837     vi = floorf(vf);
2838
2839     *du = uf - ui;
2840     *dv = vf - vi;
2841
2842     for (int i = -1; i < 3; i++) {
2843         for (int j = -1; j < 3; j++) {
2844             us[i + 1][j + 1] = av_clip(u_shift + ui + j, 0, width  - 1);
2845             vs[i + 1][j + 1] = av_clip(          vi + i, 0, height - 1);
2846         }
2847     }
2848
2849     return 1;
2850 }
2851
2852 /**
2853  * Calculate 3D coordinates on sphere for corresponding frame position in barrel facebook's format.
2854  *
2855  * @param s filter private context
2856  * @param i horizontal position on frame [0, width)
2857  * @param j vertical position on frame [0, height)
2858  * @param width frame width
2859  * @param height frame height
2860  * @param vec coordinates on sphere
2861  */
2862 static int barrel_to_xyz(const V360Context *s,
2863                          int i, int j, int width, int height,
2864                          float *vec)
2865 {
2866     const float scale = 0.99f;
2867     float l_x, l_y, l_z;
2868
2869     if (i < 4 * width / 5) {
2870         const float theta_range = M_PI_4;
2871
2872         const int ew = 4 * width / 5;
2873         const int eh = height;
2874
2875         const float phi   = ((2.f * i) / ew - 1.f) * M_PI        / scale;
2876         const float theta = ((2.f * j) / eh - 1.f) * theta_range / scale;
2877
2878         const float sin_phi   = sinf(phi);
2879         const float cos_phi   = cosf(phi);
2880         const float sin_theta = sinf(theta);
2881         const float cos_theta = cosf(theta);
2882
2883         l_x =  cos_theta * sin_phi;
2884         l_y = -sin_theta;
2885         l_z = -cos_theta * cos_phi;
2886     } else {
2887         const int ew = width  / 5;
2888         const int eh = height / 2;
2889
2890         float uf, vf;
2891
2892         if (j < eh) {   // UP
2893             uf = 2.f * (i - 4 * ew) / ew  - 1.f;
2894             vf = 2.f * (j         ) / eh - 1.f;
2895
2896             uf /= scale;
2897             vf /= scale;
2898
2899             l_x =  uf;
2900             l_y =  1.f;
2901             l_z = -vf;
2902         } else {            // DOWN
2903             uf = 2.f * (i - 4 * ew) / ew - 1.f;
2904             vf = 2.f * (j -     eh) / eh - 1.f;
2905
2906             uf /= scale;
2907             vf /= scale;
2908
2909             l_x =  uf;
2910             l_y = -1.f;
2911             l_z =  vf;
2912         }
2913     }
2914
2915     vec[0] = l_x;
2916     vec[1] = l_y;
2917     vec[2] = l_z;
2918
2919     normalize_vector(vec);
2920
2921     return 1;
2922 }
2923
2924 /**
2925  * Calculate frame position in barrel facebook's format for corresponding 3D coordinates on sphere.
2926  *
2927  * @param s filter private context
2928  * @param vec coordinates on sphere
2929  * @param width frame width
2930  * @param height frame height
2931  * @param us horizontal coordinates for interpolation window
2932  * @param vs vertical coordinates for interpolation window
2933  * @param du horizontal relative coordinate
2934  * @param dv vertical relative coordinate
2935  */
2936 static int xyz_to_barrel(const V360Context *s,
2937                          const float *vec, int width, int height,
2938                          int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2939 {
2940     const float scale = 0.99f;
2941
2942     const float phi   = atan2f(vec[0], -vec[2]) * s->input_mirror_modifier[0];
2943     const float theta = asinf(-vec[1]) * s->input_mirror_modifier[1];
2944     const float theta_range = M_PI_4;
2945
2946     int ew, eh;
2947     int u_shift, v_shift;
2948     float uf, vf;
2949     int ui, vi;
2950
2951     if (theta > -theta_range && theta < theta_range) {
2952         ew = 4 * width / 5;
2953         eh = height;
2954
2955         u_shift = s->ih_flip ? width / 5 : 0;
2956         v_shift = 0;
2957
2958         uf = (phi   / M_PI        * scale + 1.f) * ew / 2.f;
2959         vf = (theta / theta_range * scale + 1.f) * eh / 2.f;
2960     } else {
2961         ew = width  / 5;
2962         eh = height / 2;
2963
2964         u_shift = s->ih_flip ? 0 : 4 * ew;
2965
2966         if (theta < 0.f) {  // UP
2967             uf =  vec[0] / vec[1];
2968             vf = -vec[2] / vec[1];
2969             v_shift = 0;
2970         } else {            // DOWN
2971             uf = -vec[0] / vec[1];
2972             vf = -vec[2] / vec[1];
2973             v_shift = eh;
2974         }
2975
2976         uf *= s->input_mirror_modifier[0] * s->input_mirror_modifier[1];
2977         vf *= s->input_mirror_modifier[1];
2978
2979         uf = 0.5f * ew * (uf * scale + 1.f);
2980         vf = 0.5f * eh * (vf * scale + 1.f);
2981     }
2982
2983     ui = floorf(uf);
2984     vi = floorf(vf);
2985
2986     *du = uf - ui;
2987     *dv = vf - vi;
2988
2989     for (int i = -1; i < 3; i++) {
2990         for (int j = -1; j < 3; j++) {
2991             us[i + 1][j + 1] = u_shift + av_clip(ui + j, 0, ew - 1);
2992             vs[i + 1][j + 1] = v_shift + av_clip(vi + i, 0, eh - 1);
2993         }
2994     }
2995
2996     return 1;
2997 }
2998
2999 static void multiply_matrix(float c[3][3], const float a[3][3], const float b[3][3])
3000 {
3001     for (int i = 0; i < 3; i++) {
3002         for (int j = 0; j < 3; j++) {
3003             float sum = 0.f;
3004
3005             for (int k = 0; k < 3; k++)
3006                 sum += a[i][k] * b[k][j];
3007
3008             c[i][j] = sum;
3009         }
3010     }
3011 }
3012
3013 /**
3014  * Calculate rotation matrix for yaw/pitch/roll angles.
3015  */
3016 static inline void calculate_rotation_matrix(float yaw, float pitch, float roll,
3017                                              float rot_mat[3][3],
3018                                              const int rotation_order[3])
3019 {
3020     const float yaw_rad   = yaw   * M_PI / 180.f;
3021     const float pitch_rad = pitch * M_PI / 180.f;
3022     const float roll_rad  = roll  * M_PI / 180.f;
3023
3024     const float sin_yaw   = sinf(-yaw_rad);
3025     const float cos_yaw   = cosf(-yaw_rad);
3026     const float sin_pitch = sinf(pitch_rad);
3027     const float cos_pitch = cosf(pitch_rad);
3028     const float sin_roll  = sinf(roll_rad);
3029     const float cos_roll  = cosf(roll_rad);
3030
3031     float m[3][3][3];
3032     float temp[3][3];
3033
3034     m[0][0][0] =  cos_yaw;  m[0][0][1] = 0;          m[0][0][2] =  sin_yaw;
3035     m[0][1][0] =  0;        m[0][1][1] = 1;          m[0][1][2] =  0;
3036     m[0][2][0] = -sin_yaw;  m[0][2][1] = 0;          m[0][2][2] =  cos_yaw;
3037
3038     m[1][0][0] = 1;         m[1][0][1] = 0;          m[1][0][2] =  0;
3039     m[1][1][0] = 0;         m[1][1][1] = cos_pitch;  m[1][1][2] = -sin_pitch;
3040     m[1][2][0] = 0;         m[1][2][1] = sin_pitch;  m[1][2][2] =  cos_pitch;
3041
3042     m[2][0][0] = cos_roll;  m[2][0][1] = -sin_roll;  m[2][0][2] =  0;
3043     m[2][1][0] = sin_roll;  m[2][1][1] =  cos_roll;  m[2][1][2] =  0;
3044     m[2][2][0] = 0;         m[2][2][1] =  0;         m[2][2][2] =  1;
3045
3046     multiply_matrix(temp, m[rotation_order[0]], m[rotation_order[1]]);
3047     multiply_matrix(rot_mat, temp, m[rotation_order[2]]);
3048 }
3049
3050 /**
3051  * Rotate vector with given rotation matrix.
3052  *
3053  * @param rot_mat rotation matrix
3054  * @param vec vector
3055  */
3056 static inline void rotate(const float rot_mat[3][3],
3057                           float *vec)
3058 {
3059     const float x_tmp = vec[0] * rot_mat[0][0] + vec[1] * rot_mat[0][1] + vec[2] * rot_mat[0][2];
3060     const float y_tmp = vec[0] * rot_mat[1][0] + vec[1] * rot_mat[1][1] + vec[2] * rot_mat[1][2];
3061     const float z_tmp = vec[0] * rot_mat[2][0] + vec[1] * rot_mat[2][1] + vec[2] * rot_mat[2][2];
3062
3063     vec[0] = x_tmp;
3064     vec[1] = y_tmp;
3065     vec[2] = z_tmp;
3066 }
3067
3068 static inline void set_mirror_modifier(int h_flip, int v_flip, int d_flip,
3069                                        float *modifier)
3070 {
3071     modifier[0] = h_flip ? -1.f : 1.f;
3072     modifier[1] = v_flip ? -1.f : 1.f;
3073     modifier[2] = d_flip ? -1.f : 1.f;
3074 }
3075
3076 static inline void mirror(const float *modifier, float *vec)
3077 {
3078     vec[0] *= modifier[0];
3079     vec[1] *= modifier[1];
3080     vec[2] *= modifier[2];
3081 }
3082
3083 static int allocate_plane(V360Context *s, int sizeof_uv, int sizeof_ker, int sizeof_mask, int p)
3084 {
3085     s->u[p] = av_calloc(s->uv_linesize[p] * s->pr_height[p], sizeof_uv);
3086     s->v[p] = av_calloc(s->uv_linesize[p] * s->pr_height[p], sizeof_uv);
3087     if (!s->u[p] || !s->v[p])
3088         return AVERROR(ENOMEM);
3089     if (sizeof_ker) {
3090         s->ker[p] = av_calloc(s->uv_linesize[p] * s->pr_height[p], sizeof_ker);
3091         if (!s->ker[p])
3092             return AVERROR(ENOMEM);
3093     }
3094
3095     if (sizeof_mask && !p) {
3096         s->mask = av_calloc(s->pr_width[p] * s->pr_height[p], sizeof_mask);
3097         if (!s->mask)
3098             return AVERROR(ENOMEM);
3099     }
3100
3101     return 0;
3102 }
3103
3104 static void fov_from_dfov(int format, float d_fov, float w, float h, float *h_fov, float *v_fov)
3105 {
3106     switch (format) {
3107     case FISHEYE:
3108         {
3109             const float d = 0.5f * hypotf(w, h);
3110
3111             *h_fov = d / h * d_fov;
3112             *v_fov = d / w * d_fov;
3113         }
3114         break;
3115     case FLAT:
3116     default:
3117         {
3118             const float da = tanf(0.5 * FFMIN(d_fov, 359.f) * M_PI / 180.f);
3119             const float d = hypotf(w, h);
3120
3121             *h_fov = atan2f(da * w, d) * 360.f / M_PI;
3122             *v_fov = atan2f(da * h, d) * 360.f / M_PI;
3123
3124             if (*h_fov < 0.f)
3125                 *h_fov += 360.f;
3126             if (*v_fov < 0.f)
3127                 *v_fov += 360.f;
3128         }
3129         break;
3130     }
3131 }
3132
3133 static void set_dimensions(int *outw, int *outh, int w, int h, const AVPixFmtDescriptor *desc)
3134 {
3135     outw[1] = outw[2] = FF_CEIL_RSHIFT(w, desc->log2_chroma_w);
3136     outw[0] = outw[3] = w;
3137     outh[1] = outh[2] = FF_CEIL_RSHIFT(h, desc->log2_chroma_h);
3138     outh[0] = outh[3] = h;
3139 }
3140
3141 // Calculate remap data
3142 static av_always_inline int v360_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
3143 {
3144     V360Context *s = ctx->priv;
3145
3146     for (int p = 0; p < s->nb_allocated; p++) {
3147         const int max_value = s->max_value;
3148         const int width = s->pr_width[p];
3149         const int uv_linesize = s->uv_linesize[p];
3150         const int height = s->pr_height[p];
3151         const int in_width = s->inplanewidth[p];
3152         const int in_height = s->inplaneheight[p];
3153         const int slice_start = (height *  jobnr     ) / nb_jobs;
3154         const int slice_end   = (height * (jobnr + 1)) / nb_jobs;
3155         float du, dv;
3156         float vec[3];
3157         XYRemap rmap;
3158
3159         for (int j = slice_start; j < slice_end; j++) {
3160             for (int i = 0; i < width; i++) {
3161                 int16_t *u = s->u[p] + (j * uv_linesize + i) * s->elements;
3162                 int16_t *v = s->v[p] + (j * uv_linesize + i) * s->elements;
3163                 int16_t *ker = s->ker[p] + (j * uv_linesize + i) * s->elements;
3164                 uint8_t *mask8 = p ? NULL : s->mask + (j * s->pr_width[0] + i);
3165                 uint16_t *mask16 = p ? NULL : (uint16_t *)s->mask + (j * s->pr_width[0] + i);
3166                 int in_mask, out_mask;
3167
3168                 if (s->out_transpose)
3169                     out_mask = s->out_transform(s, j, i, height, width, vec);
3170                 else
3171                     out_mask = s->out_transform(s, i, j, width, height, vec);
3172                 av_assert1(!isnan(vec[0]) && !isnan(vec[1]) && !isnan(vec[2]));
3173                 rotate(s->rot_mat, vec);
3174                 av_assert1(!isnan(vec[0]) && !isnan(vec[1]) && !isnan(vec[2]));
3175                 normalize_vector(vec);
3176                 mirror(s->output_mirror_modifier, vec);
3177                 if (s->in_transpose)
3178                     in_mask = s->in_transform(s, vec, in_height, in_width, rmap.v, rmap.u, &du, &dv);
3179                 else
3180                     in_mask = s->in_transform(s, vec, in_width, in_height, rmap.u, rmap.v, &du, &dv);
3181                 av_assert1(!isnan(du) && !isnan(dv));
3182                 s->calculate_kernel(du, dv, &rmap, u, v, ker);
3183
3184                 if (!p && s->mask) {
3185                     if (s->mask_size == 1) {
3186                         mask8[0] = 255 * (out_mask & in_mask);
3187                     } else {
3188                         mask16[0] = max_value * (out_mask & in_mask);
3189                     }
3190                 }
3191             }
3192         }
3193     }
3194
3195     return 0;
3196 }
3197
3198 static int config_output(AVFilterLink *outlink)
3199 {
3200     AVFilterContext *ctx = outlink->src;
3201     AVFilterLink *inlink = ctx->inputs[0];
3202     V360Context *s = ctx->priv;
3203     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
3204     const int depth = desc->comp[0].depth;
3205     const int sizeof_mask = s->mask_size = (depth + 7) >> 3;
3206     int sizeof_uv;
3207     int sizeof_ker;
3208     int err;
3209     int h, w;
3210     int in_offset_h, in_offset_w;
3211     int out_offset_h, out_offset_w;
3212     float hf, wf;
3213     int (*prepare_out)(AVFilterContext *ctx);
3214     int have_alpha;
3215
3216     s->max_value = (1 << depth) - 1;
3217     s->input_mirror_modifier[0] = s->ih_flip ? -1.f : 1.f;
3218     s->input_mirror_modifier[1] = s->iv_flip ? -1.f : 1.f;
3219
3220     switch (s->interp) {
3221     case NEAREST:
3222         s->calculate_kernel = nearest_kernel;
3223         s->remap_slice = depth <= 8 ? remap1_8bit_slice : remap1_16bit_slice;
3224         s->elements = 1;
3225         sizeof_uv = sizeof(int16_t) * s->elements;
3226         sizeof_ker = 0;
3227         break;
3228     case BILINEAR:
3229         s->calculate_kernel = bilinear_kernel;
3230         s->remap_slice = depth <= 8 ? remap2_8bit_slice : remap2_16bit_slice;
3231         s->elements = 2 * 2;
3232         sizeof_uv = sizeof(int16_t) * s->elements;
3233         sizeof_ker = sizeof(int16_t) * s->elements;
3234         break;
3235     case BICUBIC:
3236         s->calculate_kernel = bicubic_kernel;
3237         s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
3238         s->elements = 4 * 4;
3239         sizeof_uv = sizeof(int16_t) * s->elements;
3240         sizeof_ker = sizeof(int16_t) * s->elements;
3241         break;
3242     case LANCZOS:
3243         s->calculate_kernel = lanczos_kernel;
3244         s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
3245         s->elements = 4 * 4;
3246         sizeof_uv = sizeof(int16_t) * s->elements;
3247         sizeof_ker = sizeof(int16_t) * s->elements;
3248         break;
3249     case SPLINE16:
3250         s->calculate_kernel = spline16_kernel;
3251         s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
3252         s->elements = 4 * 4;
3253         sizeof_uv = sizeof(int16_t) * s->elements;
3254         sizeof_ker = sizeof(int16_t) * s->elements;
3255         break;
3256     case GAUSSIAN:
3257         s->calculate_kernel = gaussian_kernel;
3258         s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
3259         s->elements = 4 * 4;
3260         sizeof_uv = sizeof(int16_t) * s->elements;
3261         sizeof_ker = sizeof(int16_t) * s->elements;
3262         break;
3263     default:
3264         av_assert0(0);
3265     }
3266
3267     ff_v360_init(s, depth);
3268
3269     for (int order = 0; order < NB_RORDERS; order++) {
3270         const char c = s->rorder[order];
3271         int rorder;
3272
3273         if (c == '\0') {
3274             av_log(ctx, AV_LOG_WARNING,
3275                    "Incomplete rorder option. Direction for all 3 rotation orders should be specified. Switching to default rorder.\n");
3276             s->rotation_order[0] = YAW;
3277             s->rotation_order[1] = PITCH;
3278             s->rotation_order[2] = ROLL;
3279             break;
3280         }
3281
3282         rorder = get_rorder(c);
3283         if (rorder == -1) {
3284             av_log(ctx, AV_LOG_WARNING,
3285                    "Incorrect rotation order symbol '%c' in rorder option. Switching to default rorder.\n", c);
3286             s->rotation_order[0] = YAW;
3287             s->rotation_order[1] = PITCH;
3288             s->rotation_order[2] = ROLL;
3289             break;
3290         }
3291
3292         s->rotation_order[order] = rorder;
3293     }
3294
3295     switch (s->in_stereo) {
3296     case STEREO_2D:
3297         w = inlink->w;
3298         h = inlink->h;
3299         in_offset_w = in_offset_h = 0;
3300         break;
3301     case STEREO_SBS:
3302         w = inlink->w / 2;
3303         h = inlink->h;
3304         in_offset_w = w;
3305         in_offset_h = 0;
3306         break;
3307     case STEREO_TB:
3308         w = inlink->w;
3309         h = inlink->h / 2;
3310         in_offset_w = 0;
3311         in_offset_h = h;
3312         break;
3313     default:
3314         av_assert0(0);
3315     }
3316
3317     set_dimensions(s->inplanewidth, s->inplaneheight, w, h, desc);
3318     set_dimensions(s->in_offset_w, s->in_offset_h, in_offset_w, in_offset_h, desc);
3319
3320     s->in_width = s->inplanewidth[0];
3321     s->in_height = s->inplaneheight[0];
3322
3323     if (s->id_fov > 0.f)
3324         fov_from_dfov(s->in, s->id_fov, w, h, &s->ih_fov, &s->iv_fov);
3325
3326     if (s->in_transpose)
3327         FFSWAP(int, s->in_width, s->in_height);
3328
3329     switch (s->in) {
3330     case EQUIRECTANGULAR:
3331         s->in_transform = xyz_to_equirect;
3332         err = 0;
3333         wf = w;
3334         hf = h;
3335         break;
3336     case CUBEMAP_3_2:
3337         s->in_transform = xyz_to_cube3x2;
3338         err = prepare_cube_in(ctx);
3339         wf = w / 3.f * 4.f;
3340         hf = h;
3341         break;
3342     case CUBEMAP_1_6:
3343         s->in_transform = xyz_to_cube1x6;
3344         err = prepare_cube_in(ctx);
3345         wf = w * 4.f;
3346         hf = h / 3.f;
3347         break;
3348     case CUBEMAP_6_1:
3349         s->in_transform = xyz_to_cube6x1;
3350         err = prepare_cube_in(ctx);
3351         wf = w / 3.f * 2.f;
3352         hf = h * 2.f;
3353         break;
3354     case EQUIANGULAR:
3355         s->in_transform = xyz_to_eac;
3356         err = prepare_eac_in(ctx);
3357         wf = w;
3358         hf = h / 9.f * 8.f;
3359         break;
3360     case FLAT:
3361         s->in_transform = xyz_to_flat;
3362         err = prepare_flat_in(ctx);
3363         wf = w;
3364         hf = h;
3365         break;
3366     case PERSPECTIVE:
3367     case PANNINI:
3368         av_log(ctx, AV_LOG_ERROR, "Supplied format is not accepted as input.\n");
3369         return AVERROR(EINVAL);
3370     case DUAL_FISHEYE:
3371         s->in_transform = xyz_to_dfisheye;
3372         err = 0;
3373         wf = w;
3374         hf = h;
3375         break;
3376     case BARREL:
3377         s->in_transform = xyz_to_barrel;
3378         err = 0;
3379         wf = w / 5.f * 4.f;
3380         hf = h;
3381         break;
3382     case STEREOGRAPHIC:
3383         s->in_transform = xyz_to_stereographic;
3384         err = prepare_stereographic_in(ctx);
3385         wf = w;
3386         hf = h / 2.f;
3387         break;
3388     case MERCATOR:
3389         s->in_transform = xyz_to_mercator;
3390         err = 0;
3391         wf = w;
3392         hf = h / 2.f;
3393         break;
3394     case BALL:
3395         s->in_transform = xyz_to_ball;
3396         err = 0;
3397         wf = w;
3398         hf = h / 2.f;
3399         break;
3400     case HAMMER:
3401         s->in_transform = xyz_to_hammer;
3402         err = 0;
3403         wf = w;
3404         hf = h;
3405         break;
3406     case SINUSOIDAL:
3407         s->in_transform = xyz_to_sinusoidal;
3408         err = 0;
3409         wf = w;
3410         hf = h;
3411         break;
3412     case FISHEYE:
3413         s->in_transform = xyz_to_fisheye;
3414         err = prepare_fisheye_in(ctx);
3415         wf = w * 2;
3416         hf = h;
3417         break;
3418     case CYLINDRICAL:
3419         s->in_transform = xyz_to_cylindrical;
3420         err = prepare_cylindrical_in(ctx);
3421         wf = w;
3422         hf = h * 2.f;
3423         break;
3424     case TETRAHEDRON:
3425         s->in_transform = xyz_to_tetrahedron;
3426         err = 0;
3427         wf = w;
3428         hf = h;
3429         break;
3430     default:
3431         av_log(ctx, AV_LOG_ERROR, "Specified input format is not handled.\n");
3432         return AVERROR_BUG;
3433     }
3434
3435     if (err != 0) {
3436         return err;
3437     }
3438
3439     switch (s->out) {
3440     case EQUIRECTANGULAR:
3441         s->out_transform = equirect_to_xyz;
3442         prepare_out = NULL;
3443         w = lrintf(wf);
3444         h = lrintf(hf);
3445         break;
3446     case CUBEMAP_3_2:
3447         s->out_transform = cube3x2_to_xyz;
3448         prepare_out = prepare_cube_out;
3449         w = lrintf(wf / 4.f * 3.f);
3450         h = lrintf(hf);
3451         break;
3452     case CUBEMAP_1_6:
3453         s->out_transform = cube1x6_to_xyz;
3454         prepare_out = prepare_cube_out;
3455         w = lrintf(wf / 4.f);
3456         h = lrintf(hf * 3.f);
3457         break;
3458     case CUBEMAP_6_1:
3459         s->out_transform = cube6x1_to_xyz;
3460         prepare_out = prepare_cube_out;
3461         w = lrintf(wf / 2.f * 3.f);
3462         h = lrintf(hf / 2.f);
3463         break;
3464     case EQUIANGULAR:
3465         s->out_transform = eac_to_xyz;
3466         prepare_out = prepare_eac_out;
3467         w = lrintf(wf);
3468         h = lrintf(hf / 8.f * 9.f);
3469         break;
3470     case FLAT:
3471         s->out_transform = flat_to_xyz;
3472         prepare_out = prepare_flat_out;
3473         w = lrintf(wf);
3474         h = lrintf(hf);
3475         break;
3476     case DUAL_FISHEYE:
3477         s->out_transform = dfisheye_to_xyz;
3478         prepare_out = NULL;
3479         w = lrintf(wf);
3480         h = lrintf(hf);
3481         break;
3482     case BARREL:
3483         s->out_transform = barrel_to_xyz;
3484         prepare_out = NULL;
3485         w = lrintf(wf / 4.f * 5.f);
3486         h = lrintf(hf);
3487         break;
3488     case STEREOGRAPHIC:
3489         s->out_transform = stereographic_to_xyz;
3490         prepare_out = prepare_stereographic_out;
3491         w = lrintf(wf);
3492         h = lrintf(hf * 2.f);
3493         break;
3494     case MERCATOR:
3495         s->out_transform = mercator_to_xyz;
3496         prepare_out = NULL;
3497         w = lrintf(wf);
3498         h = lrintf(hf * 2.f);
3499         break;
3500     case BALL:
3501         s->out_transform = ball_to_xyz;
3502         prepare_out = NULL;
3503         w = lrintf(wf);
3504         h = lrintf(hf * 2.f);
3505         break;
3506     case HAMMER:
3507         s->out_transform = hammer_to_xyz;
3508         prepare_out = NULL;
3509         w = lrintf(wf);
3510         h = lrintf(hf);
3511         break;
3512     case SINUSOIDAL:
3513         s->out_transform = sinusoidal_to_xyz;
3514         prepare_out = NULL;
3515         w = lrintf(wf);
3516         h = lrintf(hf);
3517         break;
3518     case FISHEYE:
3519         s->out_transform = fisheye_to_xyz;
3520         prepare_out = prepare_fisheye_out;
3521         w = lrintf(wf * 0.5f);
3522         h = lrintf(hf);
3523         break;
3524     case PANNINI:
3525         s->out_transform = pannini_to_xyz;
3526         prepare_out = NULL;
3527         w = lrintf(wf);
3528         h = lrintf(hf);
3529         break;
3530     case CYLINDRICAL:
3531         s->out_transform = cylindrical_to_xyz;
3532         prepare_out = prepare_cylindrical_out;
3533         w = lrintf(wf);
3534         h = lrintf(hf * 0.5f);
3535         break;
3536     case PERSPECTIVE:
3537         s->out_transform = perspective_to_xyz;
3538         prepare_out = NULL;
3539         w = lrintf(wf / 2.f);
3540         h = lrintf(hf);
3541         break;
3542     case TETRAHEDRON:
3543         s->out_transform = tetrahedron_to_xyz;
3544         prepare_out = NULL;
3545         w = lrintf(wf);
3546         h = lrintf(hf);
3547         break;
3548     default:
3549         av_log(ctx, AV_LOG_ERROR, "Specified output format is not handled.\n");
3550         return AVERROR_BUG;
3551     }
3552
3553     // Override resolution with user values if specified
3554     if (s->width > 0 && s->height > 0) {
3555         w = s->width;
3556         h = s->height;
3557     } else if (s->width > 0 || s->height > 0) {
3558         av_log(ctx, AV_LOG_ERROR, "Both width and height values should be specified.\n");
3559         return AVERROR(EINVAL);
3560     } else {
3561         if (s->out_transpose)
3562             FFSWAP(int, w, h);
3563
3564         if (s->in_transpose)
3565             FFSWAP(int, w, h);
3566     }
3567
3568     if (s->d_fov > 0.f)
3569         fov_from_dfov(s->out, s->d_fov, w, h, &s->h_fov, &s->v_fov);
3570
3571     if (prepare_out) {
3572         err = prepare_out(ctx);
3573         if (err != 0)
3574             return err;
3575     }
3576
3577     set_dimensions(s->pr_width, s->pr_height, w, h, desc);
3578
3579     s->out_width = s->pr_width[0];
3580     s->out_height = s->pr_height[0];
3581
3582     if (s->out_transpose)
3583         FFSWAP(int, s->out_width, s->out_height);
3584
3585     switch (s->out_stereo) {
3586     case STEREO_2D:
3587         out_offset_w = out_offset_h = 0;
3588         break;
3589     case STEREO_SBS:
3590         out_offset_w = w;
3591         out_offset_h = 0;
3592         w *= 2;
3593         break;
3594     case STEREO_TB:
3595         out_offset_w = 0;
3596         out_offset_h = h;
3597         h *= 2;
3598         break;
3599     default:
3600         av_assert0(0);
3601     }
3602
3603     set_dimensions(s->out_offset_w, s->out_offset_h, out_offset_w, out_offset_h, desc);
3604     set_dimensions(s->planewidth, s->planeheight, w, h, desc);
3605
3606     for (int i = 0; i < 4; i++)
3607         s->uv_linesize[i] = FFALIGN(s->pr_width[i], 8);
3608
3609     outlink->h = h;
3610     outlink->w = w;
3611
3612     s->nb_planes = av_pix_fmt_count_planes(inlink->format);
3613     have_alpha   = !!(desc->flags & AV_PIX_FMT_FLAG_ALPHA);
3614
3615     if (desc->log2_chroma_h == desc->log2_chroma_w && desc->log2_chroma_h == 0) {
3616         s->nb_allocated = 1;
3617         s->map[0] = s->map[1] = s->map[2] = s->map[3] = 0;
3618     } else {
3619         s->nb_allocated = 2;
3620         s->map[0] = s->map[3] = 0;
3621         s->map[1] = s->map[2] = 1;
3622     }
3623
3624     for (int i = 0; i < s->nb_allocated; i++)
3625         allocate_plane(s, sizeof_uv, sizeof_ker, sizeof_mask * have_alpha * s->alpha, i);
3626
3627     calculate_rotation_matrix(s->yaw, s->pitch, s->roll, s->rot_mat, s->rotation_order);
3628     set_mirror_modifier(s->h_flip, s->v_flip, s->d_flip, s->output_mirror_modifier);
3629
3630     ctx->internal->execute(ctx, v360_slice, NULL, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
3631
3632     return 0;
3633 }
3634
3635 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
3636 {
3637     AVFilterContext *ctx = inlink->dst;
3638     AVFilterLink *outlink = ctx->outputs[0];
3639     V360Context *s = ctx->priv;
3640     AVFrame *out;
3641     ThreadData td;
3642
3643     out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
3644     if (!out) {
3645         av_frame_free(&in);
3646         return AVERROR(ENOMEM);
3647     }
3648     av_frame_copy_props(out, in);
3649
3650     td.in = in;
3651     td.out = out;
3652
3653     ctx->internal->execute(ctx, s->remap_slice, &td, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
3654
3655     av_frame_free(&in);
3656     return ff_filter_frame(outlink, out);
3657 }
3658
3659 static av_cold void uninit(AVFilterContext *ctx)
3660 {
3661     V360Context *s = ctx->priv;
3662
3663     for (int p = 0; p < s->nb_allocated; p++) {
3664         av_freep(&s->u[p]);
3665         av_freep(&s->v[p]);
3666         av_freep(&s->ker[p]);
3667     }
3668     av_freep(&s->mask);
3669 }
3670
3671 static const AVFilterPad inputs[] = {
3672     {
3673         .name         = "default",
3674         .type         = AVMEDIA_TYPE_VIDEO,
3675         .filter_frame = filter_frame,
3676     },
3677     { NULL }
3678 };
3679
3680 static const AVFilterPad outputs[] = {
3681     {
3682         .name         = "default",
3683         .type         = AVMEDIA_TYPE_VIDEO,
3684         .config_props = config_output,
3685     },
3686     { NULL }
3687 };
3688
3689 AVFilter ff_vf_v360 = {
3690     .name          = "v360",
3691     .description   = NULL_IF_CONFIG_SMALL("Convert 360 projection of video."),
3692     .priv_size     = sizeof(V360Context),
3693     .uninit        = uninit,
3694     .query_formats = query_formats,
3695     .inputs        = inputs,
3696     .outputs       = outputs,
3697     .priv_class    = &v360_class,
3698     .flags         = AVFILTER_FLAG_SLICE_THREADS,
3699 };