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