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