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